Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
c872220
feat: enable embedding models, add text embedding generator processor…
sal-uva Sep 16, 2026
742ccd2
feat: update llms.json
sal-uva Sep 16, 2026
791ddcc
feat:
sal-uva Sep 17, 2026
9470deb
fix: open html previews with utf-8
sal-uva Sep 17, 2026
284f05c
fix: try a few more times if embeddings fail
sal-uva Sep 17, 2026
3949e50
feat: new 'Extract thumbnails' processor which is used by the embeddi…
sal-uva Sep 17, 2026
9c1fe3b
feat: split dimensionality reduction into its own process, add a new …
sal-uva Sep 23, 2026
0ffe31c
fix: remove VERSION from AGENTS.md
sal-uva Sep 25, 2026
81e3560
feat: new processor, Cluster embeddings, which can cluster embeddings…
sal-uva Sep 25, 2026
b227d11
fix: instructions weren't properly sent to vLLM with media-type embed…
sal-uva Sep 25, 2026
3d273e3
fix: Extract thumbnails -> Generate thumbnails
sal-uva Sep 25, 2026
4807a26
feat: Use cluster colors in the Plot embeddings processors
sal-uva Sep 25, 2026
7f228a3
fix: ruff stuff
sal-uva Sep 25, 2026
835f783
fix: tests for embedding processors and llm embedding client
sal-uva Sep 25, 2026
7cba15f
Allow missing 'source' field in tweet data when mapping
stijn-uva Oct 1, 2026
7e5e329
fix: fail on 10 CONSECUTIVE media embeddings, not in total. Also pad …
sal-uva Oct 1, 2026
433026c
download_videos: YTDLP record video record immediately on download (i…
dale-wahl Oct 6, 2026
99e9305
user.py: when deleting user, only delete dataset if they are the sole…
dale-wahl Oct 7, 2026
00d782f
api_tool: only owners can export datasets (contains full db record an…
dale-wahl Oct 7, 2026
2b0ce7c
views_dataset - show_result: switch is_admin for privileges.admin.can…
dale-wahl Oct 7, 2026
6daa05b
explorer: ensure only owners can add/edit annotations
dale-wahl Oct 7, 2026
86fc7fa
hide clarifai (company and api closed); reorder get_available_process…
dale-wahl Oct 7, 2026
72e1b2d
iterate_items: check for file existence first and report; merge_datas…
dale-wahl Oct 8, 2026
21e2f1b
dataset.py _iterate_items: raise if CSV provides bad row (more values…
dale-wahl Oct 8, 2026
deca79f
column_filter: revert to only strip strings
dale-wahl Oct 8, 2026
33dcf06
Add some prompts to the library
stijn-uva Oct 9, 2026
00a104a
UvA flavour LLM client until they figure things out
stijn-uva Oct 9, 2026
6286c97
More UvA LiteLLM hotfixing
stijn-uva Oct 9, 2026
55386c5
Crisis averted
stijn-uva Oct 9, 2026
1c12cff
No changing username in admin form (for now; we'd need to update ever…
dale-wahl Oct 12, 2026
193daeb
Allow admin to get password reset for any user (not just users withou…
dale-wahl Oct 12, 2026
b2bd6e5
Hide send password link if username is not password
dale-wahl Oct 12, 2026
567bb5d
manager: check db connection prior to cancelling queries (db is added…
dale-wahl Oct 12, 2026
c1a22ad
api.py: don't die if unable to handle request
dale-wahl Oct 12, 2026
b4d966b
move looks_like_email_address to helpers (and use it in processor, cr…
dale-wahl Oct 12, 2026
4879315
proxied_requests: remove proxy credentials prior to logging
dale-wahl Oct 12, 2026
ed61d1b
download_tiktok_video: update to check tiktok response status code, c…
dale-wahl Oct 12, 2026
4593dda
check_processor: skip child dataset if parent deleted; (update doc st…
dale-wahl Oct 12, 2026
a5f6e08
image_wall_w_text: refactor compatibility check so it fails faster an…
dale-wahl Oct 12, 2026
6d9ed74
download_videos: log failures; better fallback URLs (don't use ural.u…
dale-wahl Oct 12, 2026
058e127
preset: list dataset logs at end of preset's log for reference (since…
dale-wahl Oct 12, 2026
83cf437
datset: add get_log_url method
dale-wahl Oct 12, 2026
6eac68b
helpers: split_urls: handle comma + space URL combinations
dale-wahl Oct 12, 2026
25889e2
wrap ural.urls_from_text to fix bug with usernames spliting URLs and …
dale-wahl Oct 12, 2026
5d8522a
urls_in_text and split_urls clarify doc strings
dale-wahl Oct 12, 2026
0e892f6
stylesheet: fix odd button style in row of buttons due to tooltip
dale-wahl Oct 12, 2026
ad5c139
feat: embedding stream processor and some memory fixes for clustering…
sal-uva Oct 13, 2026
0a51ad8
video_timelines: harden get_video_labels (handle -import, handle non-…
dale-wahl Oct 13, 2026
620ee92
refactor import_media to allow video_archive file
dale-wahl Oct 13, 2026
20a06a5
delete annotate_text (duplicate of prompt compass)
dale-wahl Oct 13, 2026
a0ba423
video_wall: ffprobe use json output to better select attributes (ffpr…
dale-wahl Oct 13, 2026
451220d
search_rednote: update mapper for new video stream version format
dale-wahl Oct 13, 2026
562d1c9
feat: set min_samples to reduce memory for HBDSCAN
sal-uva Oct 13, 2026
3de2cb3
ensure batched items w/ annotation flush at end (fix for edge cases w…
dale-wahl Oct 14, 2026
8f0ddb9
update other ffprobe calls to use JSON and avoid split on comma errors
dale-wahl Oct 14, 2026
7bde7f3
feat: a filter for thumbnail downloading
sal-uva Oct 14, 2026
690e3c2
feat: latent space path processor
sal-uva Oct 14, 2026
c47e777
feat: make sure clustering runds in child thread--sklearn can eat all…
sal-uva Oct 14, 2026
3d3ea6f
feat: set explorer templates in __init__ rather than using odd prefix…
sal-uva Oct 14, 2026
15f5aca
fix: show correct counter for media thumbnails with filter
sal-uva Oct 14, 2026
8169e79
feat: add scroll button and autoplay to tiktok explorer templates
sal-uva Oct 14, 2026
27eba24
feat: enable embedding models, add text embedding generator processor…
sal-uva Sep 16, 2026
3bdd525
feat: update llms.json
sal-uva Sep 16, 2026
5eeef7b
feat:
sal-uva Sep 17, 2026
f19ee02
fix: open html previews with utf-8
sal-uva Sep 17, 2026
8b66e6d
fix: try a few more times if embeddings fail
sal-uva Sep 17, 2026
8c07057
feat: new 'Extract thumbnails' processor which is used by the embeddi…
sal-uva Sep 17, 2026
ea5c031
feat: split dimensionality reduction into its own process, add a new …
sal-uva Sep 23, 2026
cda7ba7
fix: remove VERSION from AGENTS.md
sal-uva Sep 25, 2026
592abff
feat: new processor, Cluster embeddings, which can cluster embeddings…
sal-uva Sep 25, 2026
1e40a43
fix: instructions weren't properly sent to vLLM with media-type embed…
sal-uva Sep 25, 2026
d47ea5d
fix: Extract thumbnails -> Generate thumbnails
sal-uva Sep 25, 2026
06ad75c
feat: Use cluster colors in the Plot embeddings processors
sal-uva Sep 25, 2026
e5c70e0
fix: ruff stuff
sal-uva Sep 25, 2026
f713740
fix: tests for embedding processors and llm embedding client
sal-uva Sep 25, 2026
801519e
fix: fail on 10 CONSECUTIVE media embeddings, not in total. Also pad …
sal-uva Oct 1, 2026
9dc4e51
feat: embedding stream processor and some memory fixes for clustering…
sal-uva Oct 13, 2026
29d0ac3
feat: set min_samples to reduce memory for HBDSCAN
sal-uva Oct 13, 2026
9439512
feat: a filter for thumbnail downloading
sal-uva Oct 14, 2026
ab440f0
feat: latent space path processor
sal-uva Oct 14, 2026
41e2cfc
feat: make sure clustering runds in child thread--sklearn can eat all…
sal-uva Oct 14, 2026
a68709e
feat: set explorer templates in __init__ rather than using odd prefix…
sal-uva Oct 14, 2026
af20d37
fix: show correct counter for media thumbnails with filter
sal-uva Oct 14, 2026
9e27f4c
feat: add scroll button and autoplay to tiktok explorer templates
sal-uva Oct 14, 2026
ecf249a
Merge branch 'embedding-llms' of https://github.com/digitalmethodsini…
sal-uva Oct 15, 2026
3b4a43f
feat: always draw thumbnails on top of dots in embedding graphs
sal-uva Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Invocation (the plain `docker compose` command uses `docker-compose.yml`):
- **Extensions**: Installed under `config/extensions/`. Each extension can include its own `requirements.txt` (auto-installed by `setup.py`). Enabled/disabled via `extensions.enabled` setting.

## Versioning and Migrations
- The current version is stored in the `VERSION` file (currently `1.53`). **Do not edit this file casually.**
- The current version is stored in the `VERSION` file. **Do not edit this file casually.**
- `config/.current-version` tracks the last-migrated version for the running instance.
- `helper-scripts/migrate.py` compares `VERSION` to `.current-version` and runs the appropriate `migrate-X.XX-X.XX.py` scripts from `helper-scripts/migrate/` in sequence.
- Migration scripts handle database schema changes, data transformations, and config updates between versions.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
1.57
1.58

This file should not be modified. It is used by 4CAT to determine whether it
needs to run migration scripts to e.g. update the database structure to a more
Expand Down
7 changes: 5 additions & 2 deletions backend/lib/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,11 @@ def request_interrupt(self, interrupt_level, job):
for queue_id, worker in self.iterate_active_workers():
if worker.job.data["id"] == job.data["id"]:
# first cancel any interruptable postgres queries for this job's worker
while True:
active_queries = self.queue.get_all_jobs("cancel-pg-query", remote_id=worker.db.appname, restrict_claimable=False)
# a worker that has not connected to the database yet (it is still
# starting up, or failed to connect) has no queries to cancel
worker_db = getattr(worker, "db", None)
while worker_db:
active_queries = self.queue.get_all_jobs("cancel-pg-query", remote_id=worker_db.appname, restrict_claimable=False)
if not active_queries:
# all cancellation jobs have been run
break
Expand Down
27 changes: 13 additions & 14 deletions backend/lib/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import abc
import csv
import os
import re
import time

from pathlib import PurePath, Path
Expand All @@ -19,7 +18,7 @@
from common.lib.dataset import DataSet, StatusType
from common.lib.compatibility import Compatibility
from common.lib.fourcat_module import FourcatModule
from common.lib.helpers import get_software_commit, remove_nuls, send_email, hash_to_md5
from common.lib.helpers import get_software_commit, remove_nuls, send_email, hash_to_md5, looks_like_email_address
from common.lib.exceptions import (WorkerInterruptedException, ProcessorInterruptedException, ProcessorException,
DataSetException, MapItemException, AnnotationException)
from common.config_manager import ConfigWrapper
Expand Down Expand Up @@ -372,7 +371,17 @@ def after_process(self):
# already finished, could happen (though it shouldn't)
pass

surrogate.update_status(self.dataset.get_status())
# status updates of the steps are copied to the preset, so it usually has this status already;
# setting it again would only repeat the line in the preset's log
if surrogate.get_status() != self.dataset.get_status():
surrogate.update_status(self.dataset.get_status())

# the steps of a preset are hidden, and their logs have more details than the preset's own
surrogate.log("Each step of this preset has its own log, with more details (e.g. why individual items failed):")
for step in surrogate.get_all_children():
step_processor = step.get_producer_processor()
step_name = step_processor.title if step_processor else step.type
surrogate.log(f" {step_name}: {step.get_log_url()}")

except DataSetException:
# dataset with key to attach to doesn't exist...
Expand All @@ -390,17 +399,7 @@ def after_process(self):
if self.config.get('mail.server') and self.dataset.get_parameters().get("email-complete", False):
owner = self.dataset.get_parameters().get("email-complete", False)
# Check that username is email address
if re.match(r"[^@]+\@.*?\.[a-zA-Z]+", owner):
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from smtplib import SMTPException
import socket
import html2text

if self.config.get('mail.server') and self.dataset.get_parameters().get("email-complete", False):
owner = self.dataset.get_parameters().get("email-complete", False)
# Check that username is email address
if re.match(r"[^@]+\@.*?\.[a-zA-Z]+", owner):
if looks_like_email_address(owner):
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from smtplib import SMTPException
Expand Down
49 changes: 37 additions & 12 deletions backend/lib/proxied_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,45 @@ def wrapper(self, *args, **kwargs):

return wrapper


def remove_proxy_credentials(proxy_url):
"""
Remove the username and password from a proxy URL

Proxy URLs can contain a username and password, as in
`http://user:password@host:port`. Processors may write the proxy they
were given to dataset logs or results that users can see, and the backend
log may be forwarded elsewhere (e.g. to Slack), so use this on any proxy
URL that is passed on to processors or written to a log.

:param str|None proxy_url: Proxy URL
:return str|None: The same URL, without anything before the last `@`
"""
if not proxy_url:
return proxy_url

scheme, separator, rest = proxy_url.partition("://")
if not separator:
scheme, rest = "", proxy_url

return scheme + separator + rest.rpartition("@")[2]

class FailedProxiedRequest:
"""
A delegated request that has failed for whatever reason

The failure context (usually the exception) is stored in the `context`
property. We also keep track of the proxy URL that serviced the request so
downstream consumers can make informed retry decisions.
downstream consumers can make informed retry decisions. Any username and
password are removed from that URL.
"""

context = None
proxy_url = None

def __init__(self, context=None, proxy_url=None):
self.context = context
self.proxy_url = proxy_url
self.proxy_url = remove_proxy_credentials(proxy_url)


class NoProxiesAvailableError(Exception):
Expand Down Expand Up @@ -175,7 +199,7 @@ def release_cooled_off(self):
and request.timestamp_finished < time.time() - self.COOLOFF
):
self.log.debug(
f"Releasing proxy {self.proxy_url} for host name {hostname}"
f"Releasing proxy {remove_proxy_credentials(self.proxy_url)} for host name {hostname}"
)
self.hostnames[hostname].running.remove(request)

Expand Down Expand Up @@ -214,7 +238,7 @@ def claim_for(self, url):
request.timestamp_finished = 0
self.hostnames[hostname].running.append(request)
self.log.debug(
f"Claiming proxy {self.proxy_url} for host name {hostname} ({len(self.hostnames[hostname].running)} of {self.MAX_CONCURRENT_PER_HOST} for host)"
f"Claiming proxy {remove_proxy_credentials(self.proxy_url)} for host name {hostname} ({len(self.hostnames[hostname].running)} of {self.MAX_CONCURRENT_PER_HOST} for host)"
)
return self
else:
Expand Down Expand Up @@ -465,11 +489,11 @@ def _update_proxy_pool(self):
if proxy_url in self.proxy_health:
del self.proxy_health[proxy_url]
self.proxies_pending_removal.discard(proxy_url)
self.log.info(f"Removed proxy {proxy_url} (no longer in config)")
self.log.info(f"Removed proxy {remove_proxy_credentials(proxy_url)} (no longer in config)")
else:
# Has active requests - mark for removal but don't delete yet
self.proxies_pending_removal.add(proxy_url)
self.log.warning(f"Proxy {proxy_url} marked for removal (has active requests, will not accept new requests)")
self.log.warning(f"Proxy {remove_proxy_credentials(proxy_url)} marked for removal (has active requests, will not accept new requests)")

# Update settings for existing proxies
for proxy_url in proxies_to_keep:
Expand All @@ -484,7 +508,7 @@ def _update_proxy_pool(self):
# Add new proxies
for proxy_url in proxies_to_add:
self._add_proxy_to_pool(proxy_url)
self.log.info(f"Added new proxy {proxy_url}")
self.log.info(f"Added new proxy {remove_proxy_credentials(proxy_url)}")

if proxies_to_add or proxies_to_remove:
self.log.info(f"Proxy pool updated: {len(self.proxy_pool)} total proxies "
Expand Down Expand Up @@ -635,15 +659,16 @@ def manage_requests(self):
if proxy_url in self.proxy_health:
del self.proxy_health[proxy_url]
self.proxies_pending_removal.discard(proxy_url)
self.log.info(f"Removed proxy {proxy_url} (completed all active requests)")
self.log.info(f"Removed proxy {remove_proxy_credentials(proxy_url)} (completed all active requests)")
try:
response = url_metadata.proxied.request.result()
# annotate the response so processors can see which
# proxy (if any) handled the request
# proxy (if any) handled the request, without its
# username and password
setattr(
response,
"_4cat_proxy",
url_metadata.proxied.proxy.proxy_url,
remove_proxy_credentials(url_metadata.proxied.proxy.proxy_url),
)
url_metadata.proxied.result = response

Expand All @@ -654,7 +679,7 @@ def manage_requests(self):
probe_succeeded = url_metadata.proxied.proxy.probe()
if probe_succeeded:
self.log.debug(
f"Proxy {proxy_url} returned a proxy error for {url}, "
f"Proxy {remove_proxy_credentials(proxy_url)} returned a proxy error for {url}, "
f"but health probe to {url_metadata.proxied.proxy.healthcheck_url} succeeded; "
f"passing original error back to requester"
)
Expand All @@ -667,7 +692,7 @@ def manage_requests(self):
if proxy_url not in self.proxy_warnings_logged:
self.proxy_warnings_logged.add(proxy_url)
self.log.warning(
f"Proxy {proxy_url} marked as unhealthy due to connection failure and failed health probe "
f"Proxy {remove_proxy_credentials(proxy_url)} marked as unhealthy due to connection failure and failed health probe "
f"({url_metadata.proxied.proxy.healthcheck_url}): {str(e)}"
)

Expand Down
15 changes: 13 additions & 2 deletions backend/workers/api.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import traceback
import socket
import time
import json
Expand Down Expand Up @@ -141,18 +142,28 @@ def api_response(self, client, address):

self.manager.log.debug("Received API request from %s:%s" % address)

request = None
try:
payload = json.loads(buffer)
if "request" not in payload:
raise InternalAPIException

response = self.process_request(payload["request"], payload)
request = payload["request"]
response = self.process_request(request, payload)
if not response:
raise InternalAPIException

response = json.dumps({"error": False, "response": response})
except (json.JSONDecodeError, InternalAPIException):
response = json.dumps({"error": "Invalid JSON"})
response = json.dumps({"error": "Invalid JSON", "response": None})
except Exception as e:
# an error while answering one request should not stop the API
# worker, because then nothing can reach the backend until it is
# restarted - so log it, answer with an error, and keep listening
stack = traceback.extract_tb(e.__traceback__)
location = "->".join([frame.filename.split("/").pop() + ":" + str(frame.lineno) for frame in stack])
self.manager.log.error(f"API request {request} raised exception {e.__class__.__name__}: {e} at {location}", frame=stack)
response = json.dumps({"error": f"Internal error ({e.__class__.__name__})", "response": None})

try:
response = client.sendall(response.encode("ascii"))
Expand Down
57 changes: 33 additions & 24 deletions common/assets/llms.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,25 @@
[
{
"model": "gpt-5.6-sol",
"name": "[OpenAI] GPT-5.6 Sol",
"model_card": "https://developers.openai.com/api/docs/models/gpt-5.6-sol",
"provider": "openai"
},{
"model": "gpt-5.6-terra",
"name": "[OpenAI] GPT-5.6 Terra",
"model_card": "https://platform.openai.com/docs/models/gpt-5.6-terra",
"provider": "openai"
},{
"model": "gpt-5.6-luna",
"name": "[OpenAI] GPT-5.6 Luna",
"model_card": "https://platform.openai.com/docs/models/gpt-5.6-luna",
"provider": "openai"
},{
"model": "gpt-5.5",
"name": "[OpenAI] GPT-5.5",
"model_card": "https://platform.openai.com/docs/models/gpt-5.5",
"provider": "openai"
},
{
"model": "gpt-5.5",
"name": "[OpenAI] GPT-5.5",
Expand Down Expand Up @@ -48,44 +69,32 @@
"provider": "google"
},
{
"model": "gemini-3.5-flash",
"name": "[Google] Gemini 3.5 Flash",
"model_card": "https://ai.google.dev/gemini-api/docs/models/gemini-3.5-flash",
"model": "gemini-3.8-flash",
"name": "[Google] Gemini 3.8 Flash",
"model_card": "https://ai.google.dev/gemini-api/docs/models/gemini-3.8-flash",
"provider": "google"
},
{
"model": "gemini-3-flash-preview",
"name": "[Google] Gemini 3 Flash",
"model_card": "https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/3-flash",
"model": "gemini-3.5-flash-lite-preview",
"name": "[Google] Gemini 3.5 Flash Lite",
"model_card": "https://docs.cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/3-5-flash-lite",
"provider": "google"
},
{
"model": "gemini-3.1-flash-lite-preview",
"name": "[Google] Gemini 3.1 Flash Lite",
"provider": "google",
"model_card": "https://docs.cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/3-1-flash-lite"
},
{
"model": "claude-fable-5",
"name": "[Anthropic] Claude Fable 5",
"model": "claude-fable-5-1",
"name": "[Anthropic] Claude Fable 5.1",
"model_card": "https://www.anthropic.com/claude/fable",
"provider": "anthropic"
},
{
"model": "claude-opus-4-8",
"name": "[Anthropic] Claude Opus 4.8",
"model_card": "https://www.anthropic.com/claude/opus",
"provider": "anthropic"
},
{
"model": "claude-opus-4-6",
"name": "[Anthropic] Claude Opus 4.6",
"model": "claude-opus-5",
"name": "[Anthropic] Claude Opus 5",
"model_card": "https://www.anthropic.com/claude/opus",
"provider": "anthropic"
},
{
"model": "claude-sonnet-4-6",
"name": "[Anthropic] Claude Sonnet 4.6 (latest)",
"model": "claude-sonnet-5",
"name": "[Anthropic] Claude Sonnet 5 (latest)",
"model_card": "https://www.anthropic.com/claude/sonnet",
"provider": "anthropic"
},
Expand Down
24 changes: 24 additions & 0 deletions common/assets/prompt_library.json
Original file line number Diff line number Diff line change
Expand Up @@ -506,5 +506,29 @@
"authors": "OpenAI (2023)",
"paper": "https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-openai-api",
"location_of_input": "replace"
},
{
"name": "political party affiliation",
"label": "party",
"prompt": [
"You will be given a set of Twitter posts from different <country> politicians, sent during the two months preceding the <year> <country> election, that is, between <date>, and <date>. Your task is to use your knowledge of <country> politics to make an educated guess on whether the poster is a <party1> or <party2>. Respond either ‘<party1>’ or ‘<party2>’. If the message does not have enough information for an educated guess, just make your best guess.",
"",
"Post: [body]"
],
"authors": "Törnberg (2024)",
"paper": "https://journals.sagepub.com/doi/10.1177/08944393241286471",
"location_of_input": "replace"
},
{
"name": "Narrative and stance",
"label": "narrative",
"prompt": [
"You are a narratologist tasked with mapping out five narratives of the provided text and categorizing the text about the Russia–Ukraine war. Please output a labeled list with two items: (1) the narrative in English (summarized within 20 words) and (2) the stance of the narrative (pro-Russian, pro-Ukraine, or neutral). Below are some examples of the potential coded languages of pro-Russian, pro-Ukraine, and neutral narratives for your reference: (1) Pro-Russian refers to Ukraine soldiers as 乌纳 (UkNazi) or 乌贼 (squid); (2) Pro-Ukraine criticizes Russian army with sarcasm 菜鹅 (Veggie Goose/Weak Russian Army), 晋军 (Jin Dynasty/Putin’s Army), 晋凉 (Cold Putin/Putin is over), 鹅 粉 (Fans of the Goose/Fans of Putin); and (3) Neutral provides factual updates and describes battlefield developments without assigning blame.",
"",
"Text: [body]"
],
"authors": "Rogers & Zhang (2024)",
"paper": "https://journals.sagepub.com/doi/10.1177/20563051241254379",
"location_of_input": "replace"
}
]
4 changes: 2 additions & 2 deletions common/lib/config_definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,13 +636,13 @@
"type": UserInput.OPTION_TEXT,
"help": "Authentication Header",
"default": "",
"tooltip": "The HTTP header used to authenticate with the server (e.g. 'X-API-KEY', 'Authorization'). Passed with the Authentication Key as value.",
"tooltip": "The HTTP header used to authenticate with the server (e.g. 'X-API-KEY', 'Authorization'). Passed with the Authentication Key as value. For a bearer token you can write either 'Authorization' with 'Bearer <key>' as the value, or 'Authorization: Bearer' with just the key.",
},
"auth_key": {
"type": UserInput.OPTION_TEXT,
"default": "",
"help": "Authentication Key",
"tooltip": "The API key to access the LLM server, if required.",
"tooltip": "The API key to access the LLM server, if required. 'Bearer <key>' for bearer tokens.",
},
}
},
Expand Down
Loading
Loading