Merge branch 'main' of github.com:LAION-AI/Open-Assistant

This commit is contained in:
Yannic Kilcher
2023-02-11 10:50:57 +01:00
59 changed files with 1271 additions and 281 deletions
+7 -8
View File
@@ -37,17 +37,16 @@ for stability**.
### 1. **Fork the OpenAssistant repository**
Fork the
`OpenAssistant`[repository](https://github.com/LAION-AI/Open-Assistant). To do
this, click the link to the repository and click "Fork" in the upper-right
Fork the [OpenAssistant repository](https://github.com/LAION-AI/Open-Assistant).
To do this, click the link to the repository and click "Fork" in the upper-right
corner. You should get an option to fork to your account, provided you are
signed into Github.
After you fork, clone the repository locally. You can do so as follows:
```bash
git clone git@github.com:<your_github_username>/OpenAssistant.git
cd OpenAssistant # enter the directory
git clone git@github.com:<your_github_username>/Open-Assistant.git
cd Open-Assistant # enter the directory
```
Next, you want to set your `upstream` location to enable you to push/pull (add
@@ -76,7 +75,7 @@ upstream git@github.com:LAION-AI/Open-Assistant.git (push)
If you do NOT have an `origin` for whatever reason, then run:
```bash
git remote add origin git@github.com:<your_github_username>/OpenAssistant.git
git remote add origin git@github.com:<your_github_username>/Open-Assistant.git
```
The goal of `upstream` is to keep your repository up-to-date to any changes that
@@ -100,7 +99,7 @@ git checkout -b <dataset_name>
:::caution
Please do not make changes on the master branch!
Please do not make changes on the main branch!
:::
@@ -114,7 +113,7 @@ The correct branch will have a asterisk \* in front of it.
### 2. **Create a development environment**
You can make an environment in any way you choose to. We highlight two possible
You can make an environment in any way you choose. We highlight two possible
options:
#### 2a) Create a conda environment
+1 -1
View File
@@ -6,7 +6,7 @@ For discussion about usage of supervised data see issue
## Motivation
An important part of making the assistant useful is to teach it to understand
and follow instructions, and to perform large set of tasks well.
and follow instructions, and to perform a large set of tasks well.
While RLHF seems like the main ingredient, using existing supervised data might
help.
+1 -1
View File
@@ -61,7 +61,7 @@ website [https://open-assistant.io/](https://open-assistant.io/). If you want to
contribute code, take a look at the
[tasks in GitHub](https://github.com/orgs/LAION-AI/projects/3) and grab one.
Take a look at this
[contributing guide](https://github.com/GuilleHoardings/Open-Assistant/blob/main/CONTRIBUTING.md).
[contributing guide](https://github.com/LAION-AI/Open-Assistant/blob/main/CONTRIBUTING.md).
## Questions about the model training website
+42
View File
@@ -18,6 +18,7 @@
"""
import os
from dataclasses import dataclass
from typing import Dict, List, Optional, Union
@@ -298,3 +299,44 @@ class AnthropicRLHF(Dataset):
context, pair = self.pairs[index]
return context, [pair]
class OAPrivate(Dataset):
"""
{
"prompt": <prompt string>,
"history": [("prompt1", "answer2"), ("prompt2", "answer2")],
"pos": <pos answer string>,
"neg_replies": [list of bad answers]
}
"""
split_name_mapping = {
"train": "rm_train.jsonl",
"test": "rm_test.jsonl",
"val": "rm_val.jsonl",
}
def __init__(self, split="train", sep_token="<sep>", data_path=".cache") -> None:
super().__init__()
import json
jsonl_file = os.path.join(data_path, self.split_name_mapping[split])
self.pairs = []
with open(jsonl_file, "r", encoding="utf-8") as f:
for line in f:
data = json.loads(line)
prefix = sep_token.join([sep_token.join(p) for p in data["history"][-2:]])
prefix += sep_token + data["prompt"]
pair = []
for neg_text in data["neg_replies"]:
pair.append((data["pos"], neg_text))
self.pairs.append((prefix, pair))
def __len__(self):
return len(self.pairs)
def __getitem__(self, index):
context, pair = self.pairs[index]
return context, pair
+7 -1
View File
@@ -115,7 +115,7 @@ def argument_parsing(parser):
def get_datasets(dataset_list: List[AnyStr], tokenizer):
from rank_datasets import AnthropicRLHF, GPTJSynthetic, HFSummary, WebGPT
from rank_datasets import AnthropicRLHF, GPTJSynthetic, HFSummary, OAPrivate, WebGPT
from torch.utils.data import ConcatDataset
train_datasets, evals = [], {}
@@ -141,5 +141,11 @@ def get_datasets(dataset_list: List[AnyStr], tokenizer):
eval = AnthropicRLHF("test", tokenizer.sep_token)
train_datasets.append(train)
evals["anthropic_rlhf"] = eval
elif "oa_private" == dataset_name:
train = OAPrivate(split="train", sep_token=tokenizer.sep_token)
eval = OAPrivate(split="val", sep_token=tokenizer.sep_token)
train_datasets.append(train)
evals["oa_private"] = eval
train = ConcatDataset(train_datasets)
return train, evals
@@ -1,8 +1,8 @@
"""
High level functions for model training
"""
from custom_datasets.prompt_dialogue import InstructionTuning, PromptGeneratedDataset
from custom_datasets.qa_datasets import SODA, JokeExplaination, QADataset, SODADialogue, WebGPT
from custom_datasets.prompt_dialogue import InstructionTuning, PrivateInstructionTuning, PromptGeneratedDataset
from custom_datasets.qa_datasets import SODA, JokeExplaination, QADataset, SODADialogue, TranslatedQA, WebGPT
from custom_datasets.summarization import SummarizationDataset
from custom_datasets.toxic_conversation import ProsocialDialogue, ProsocialDialogueExplaination
from custom_datasets.translation import WMT2019, DiveMT, TEDTalk
@@ -32,7 +32,7 @@ SUMMARIZATION_DATASETS = [
"debate_sum",
"tldr_news",
]
OTHER = ["prosocial_dialogue", "explain_prosocial", "instruct_tuning"]
OTHER = ["prosocial_dialogue", "explain_prosocial", "instruct_tuning", "private_tuning", "oa_translated"]
def train_val_dataset(dataset, val_split=0.2):
@@ -92,6 +92,12 @@ def get_one_dataset(conf, dataset_name):
elif dataset_name == "instruct_tuning":
dataset = InstructionTuning(conf.cache_dir)
train, eval = train_val_dataset(dataset, val_split=0.2)
elif dataset_name == "private_tuning":
dataset = PrivateInstructionTuning(conf.cache_dir)
train, eval = train_val_dataset(dataset, val_split=0.2)
elif dataset_name == "oa_translated":
dataset = TranslatedQA(conf.cache_dir)
train, eval = train_val_dataset(dataset, val_split=0.01)
else:
raise ValueError(f"Unknown dataset {dataset_name}")
@@ -1,3 +1,4 @@
import random
from dataclasses import dataclass
from typing import Optional, Union
@@ -76,3 +77,108 @@ class DialogueDataCollator:
batch["targets"] = torch.roll(batch["input_ids"], -1, -1)
return batch
@dataclass
class TrainDialogueDataCollator:
"""
Expects a list of texts corresponding to a sequence of [question, answer, question, answer, ...] pairs.
"""
tokenizer: PreTrainedTokenizerBase
padding: Union[bool, str, PaddingStrategy] = True
max_length: Optional[int] = None
mix_length_threshold: Optional[int] = 256
mix_probability: Optional[int] = 0.6
pad_to_multiple_of: Optional[int] = None
def __call__(self, features):
flatten_messages = []
label_masks = []
total_short_context = 0
for messages in features:
messages = list(messages)
# Add a way for the model to terminate generation
# When we predict the start of a new expected question, we want to be able to stop generation
messages.append(self.tokenizer.eos_token)
flatten_message = self.tokenizer(
"".join(messages),
truncation=True,
max_length=self.max_length,
return_offsets_mapping=True,
)
message_change_indices = np.cumsum([len(x) for x in messages[:-1]])
# for each token an integer indicating the index of the message it belongs to. Just to create the label mask.
# Label mask is true when predicting a token that is part of the answer, false otherwise.
# TEXT: Question: Hello, how are you? Answer: I am fine. Question: What is your name? Answer: My name is John. Question:
# MESSAGE_INDICES: 0 0 0 0 0 0 1 1 1 2 2 2 2 2 2 3 3 3 3 -2
# LABEL_MASK: 0 0 0 0 0 1 1 1 1 0 0 0 0 0 1 1 1 1 1 0
# If no result in next, we are predicting the last termination token(s)
message_indices = list(
map(
lambda x: next((i for i, val in enumerate(message_change_indices) if val >= x), -2),
list(map(lambda x: x[1], flatten_message["offset_mapping"])),
)
)
label_mask = np.roll(list(map(lambda x: x % 2 == 1, message_indices)), -1, -1)
try:
label_mask[[i for i in range(len(message_indices)) if message_indices[i] == -2][0] - 1] = True
except IndexError:
# due to truncation, we might not have the last termination token
label_mask[-1] = False
label_masks.append(label_mask)
if len(flatten_message["input_ids"]) < self.mix_length_threshold:
total_short_context += len(flatten_message["input_ids"])
flatten_messages.append({k: v for k, v in flatten_message.items() if k != "offset_mapping"})
# packing
if total_short_context > 2:
_flatten_messages, _label_masks = [], []
prev_short_msg, prev_short_mask = None, None
for flatten_msg, label_mask in zip(flatten_messages, label_masks):
if len(flatten_msg["input_ids"]) < self.mix_length_threshold and random.random() > 0.6:
if prev_short_msg is not None:
for key in flatten_msg.keys():
flatten_msg[key] += prev_short_msg[key]
flatten_msg[key] = flatten_msg[key][: self.max_length]
label_mask = np.concatenate([label_mask, prev_short_mask])
_label_masks.append(label_mask[: self.max_length])
_flatten_messages.append(flatten_msg)
# reset
prev_short_msg, prev_short_mask = None, None
else:
# prime
prev_short_msg, prev_short_mask = flatten_msg, label_mask
else:
_label_masks.append(label_mask)
_flatten_messages.append(flatten_msg)
if prev_short_msg is not None:
for key in flatten_msg.keys():
flatten_msg[key] += prev_short_msg[key]
flatten_msg[key] = flatten_msg[key][: self.max_length]
label_mask = np.concatenate([label_mask, prev_short_mask])[: self.max_length]
_label_masks.append(label_mask)
_flatten_messages.append(flatten_msg)
label_masks = _label_masks
flatten_messages = _flatten_messages
batch = self.tokenizer.pad(
flatten_messages,
padding=self.padding,
max_length=self.max_length,
pad_to_multiple_of=self.pad_to_multiple_of,
return_tensors="pt",
)
dim = batch["input_ids"].shape[-1]
batch["label_masks"] = torch.stack(
[F.pad(torch.tensor(x), (0, dim - len(x)), value=False) for x in label_masks]
)
batch["targets"] = torch.roll(batch["input_ids"], -1, -1)
return batch
@@ -2,7 +2,7 @@ import json
import os
from urllib.request import urlopen
from custom_datasets.formatting import format_pair
from custom_datasets.formatting import QA_SPECIAL_TOKENS, format_pair
from torch.utils.data import Dataset
@@ -102,3 +102,45 @@ class InstructionTuning(Dataset):
def __getitem__(self, index):
return format_pair(self.pairs[index])
class PrivateInstructionTuning(Dataset):
"""
We have seen some promising capabilities from instruction tuning
with the following mix of datasets that are derived from datasets
available online.
The files for this data are in json format as a list of tuples
where each tuple is (source,instruction_response_pair)
Not to be confused with unatural instruction
"""
name = "private_tuning"
filename = "oa_v3_fixed_plus_safety.jsonl"
def __init__(self, cache_dir) -> None:
super().__init__()
os.makedirs(cache_dir, exist_ok=True)
self.pairs = []
for file_link in [self.filename]:
basename = file_link.split("/")[-1]
instruction_tune_file = os.path.join(cache_dir, basename)
with open(instruction_tune_file, "r", encoding="utf-8") as f:
for line in f:
row = json.loads(line)
prefix = ""
for _, convo in enumerate(row["text"].split("User:")):
if "Assistant" in convo:
prompt, answer = convo.split("Assistant:", maxsplit=1)
answer = answer.replace("<|endoftext|>", "").strip()
self.pairs.append((prefix + QA_SPECIAL_TOKENS["Question"] + prompt, answer))
prefix += "".join(format_pair((prompt, answer)))
def __len__(self):
return len(self.pairs)
def __getitem__(self, index):
prompt, answer = self.pairs[index]
return "{}{}".format(prompt, QA_SPECIAL_TOKENS["Answer"]), answer
@@ -1,6 +1,7 @@
"""
Open / close book QA datasets
"""
import glob
import json
import os
import re
@@ -137,11 +138,13 @@ class QADataset(Dataset):
self.dataset = load_dataset(context["name"], **context["params"])
else:
raise ValueError("Unknown dataset : " + dataset)
self.length = len(self.dataset)
def __len__(self):
return len(self.dataset)
return self.length
def __getitem__(self, idx):
data = self.dataset[idx]
return format_pair(self.index_fn(data))
@@ -311,14 +314,75 @@ class JokeExplaination(Dataset):
for line in f:
data = json.loads(line)
joke = data["joke"]
explanation = data["explanation"]
# DO NOT change this
# its the data that had syntax error
explanation = data["explaination"]
self.pairs.append((joke, explanation))
if len(question) > 0 and len(answer) > 0:
self.pairs.append((question, answer))
self.length = len(self.pairs)
def __len__(self):
return len(self.pairs)
return self.length
def __getitem__(self, index):
return format_pair(self.pairs[index])
class TranslatedQA(Dataset):
"""
Translation OA v3 results
a list of non english translation of OA v3 instruction generated text in jsonl
format for each line:
{
"text": "User: ... Assistant: ....",
"meta": {"source": ... },
"translate": [
{ "round": 1, "human":"...", "answer": "..."},
...
{ "round": K, "human":"...", "answer": "..."},
]
}
Since OA contain some code we needed to reference the original text to skip these
"""
name = "oa_translated"
def __init__(self, cache_dir) -> None:
super().__init__()
os.makedirs(cache_dir, exist_ok=True)
path = os.path.join(cache_dir, self.name)
os.makedirs(path, exist_ok=True)
self.pairs = []
for translated_jsonl in glob.glob(os.path.join(path, "*.jsonl")):
with open(translated_jsonl, "r") as fin:
for line in fin:
data = json.loads(line)
if "Python " in data["text"]:
# translation currently doesn't ignore code
# so we will have to reference original text
# for ignoring the translation
continue
prefix = ""
for convo_round in data["translate"]:
human, answer = format_pair((convo_round["human"], convo_round["answer"]))
if convo_round["round"] > 2:
self.pairs.append(("{}{}{}".format(prefix, "<sep>", human), answer))
else:
self.pairs.append((human, answer))
prefix += "{}{}{}{}".format(
QA_SPECIAL_TOKENS["Question"],
convo_round["human"],
QA_SPECIAL_TOKENS["Answer"],
convo_round["answer"],
)
self.length = len(self.pairs)
def __len__(self):
return self.length
def __getitem__(self, index):
return self.pairs[index]
@@ -57,7 +57,7 @@ def index_summary_merge(text, summary):
class SummarizationDataset(Dataset):
def __init__(self, dataset, cache_dir, split, max_words=512):
self.name = dataset
if summarization_config_mapping[dataset][0] in ["billsum", "tldr_news"] & split == "validation":
if (dataset in ["billsum", "tldr_news"]) and (split == "validation"):
split = "test"
self.dataset = load_dataset(*summarization_config_mapping[dataset], cache_dir=cache_dir, split=split)
self.text_column, self.summary_column = summarization_name_mapping[dataset]
@@ -62,7 +62,8 @@ TRANSLATION_PROMPT = {
"{} how do we write in Malay",
"{} give me the malay translation",
"{} , berikan saya terjemahan dalam bahasa melayu",
"{}, Jemahan di bahasa melayu" "{}, jemahkan ayat ini kepada bahasa melayu",
"{}, Jemahan di bahasa melayu",
"{}, jemahkan ayat ini kepada bahasa melayu",
],
"en": ["{}. translate to english", "{} write in english", "english translation: '{}'"],
"ru": ["помогите мне перевести это на русский : {}", "{} перевести на русский язык", "russian translation: '{}'"],
@@ -71,24 +72,40 @@ TRANSLATION_PROMPT = {
"nl": ["{}. translate to dutch", "{} write in dutch", "dutch translation: '{}'"],
"vi": ["{}. Dịch sang tiếng việt nam", "{} write in vietnamese", "vietnamese translation: '{}'"],
"ar": ["{}. translate to arabic", "{} write in arabic", "arabic translation: '{}'"],
"es": ["{}. translate to spanish", "{} write in spanish", "spanish translation: '{}'"],
"hi": ["{}. translate to hindi", "{}. translate to bengali", "{} write in hindi", "bengali translation: '{}'"],
}
class TranslationPair(Dataset):
def __init__(self) -> None:
def __init__(self, mix_prob=0.2) -> None:
super().__init__()
self.pairs = []
self.length = -1
self.mix_prob = mix_prob
def __len__(self):
if self.length < 0:
self.length = len(self.pairs)
return len(self.pairs)
def __getitem__(self, index):
if random.random() < self.mix_prob and index > 5 and index < (self.length - 5):
additional = random.randint(0, 10) - 5
while additional == index:
additional = random.randint(0, 10) - 5
history_text = "".join(format_pair(self.pairs[additional + index]))
question, answer = self.pairs[index]
question = history_text + question
return format_pair((question, answer))
return format_pair(self.pairs[index])
class WMT2019(TranslationPair):
def __init__(self, pair="zh-en", split="train") -> None:
super().__init__()
def __init__(self, pair="zh-en", split="train", mix_prob=0.2) -> None:
super().__init__(mix_prob=mix_prob)
dataset = load_dataset("wmt19", pair)[split]
self.pairs = []
src, tgt = pair.split("-")
@@ -106,8 +123,8 @@ class DiveMT(TranslationPair):
REMAP = {"tur": "tr", "ita": "it", "ukr": "uk", "nld": "nl", "vie": "vi", "ara": "ar"}
def __init__(self, split="train") -> None:
super().__init__()
def __init__(self, split="train", mix_prob=0.2) -> None:
super().__init__(mix_prob=mix_prob)
dataset = load_dataset("GroNLP/divemt", "main")[split]
tgt, src = "tgt_text", "src_text"
for row in dataset:
@@ -129,8 +146,8 @@ class DiveMT(TranslationPair):
class TEDTalk(TranslationPair):
# NOTE: DO NOT use chinese pair, mix with traditional and cantonese, not clean
def __init__(self, pair="de-ja", split="train", year="2016") -> None:
super().__init__()
def __init__(self, pair="de-ja", split="train", year="2016", mix_prob=0.2) -> None:
super().__init__(mix_prob=mix_prob)
dataset = load_dataset("ted_talks_iwslt", language_pair=pair.split("-"), year=year)[split]
src, tgt = pair.split("-")
for row in dataset:
@@ -27,7 +27,7 @@ def test_collate_fn():
config = Namespace(cache_dir=".cache", model_name="Salesforce/codegen-2B-multi")
tokenizer = get_tokenizer(config)
collate_fn = DialogueDataCollator(tokenizer, max_length=512)
collate_fn = DialogueDataCollator(tokenizer, max_length=620)
qa_base = QA_DATASETS
summarize_base = SUMMARIZATION_DATASETS
others = ["prompt_dialogue", "webgpt", "soda", "joke", "gsm8k"]
@@ -40,11 +40,14 @@ def test_collate_fn():
dataloader = DataLoader(ConcatDataset(trains), collate_fn=collate_fn, batch_size=128)
for batch in dataloader:
print(batch.keys())
print(batch["targets"].shape[0])
print(tokenizer.decode(batch["input_ids"][0]))
print("-----")
print(tokenizer.decode(batch["targets"][0][batch["label_masks"][0]]))
assert batch["targets"].shape[1] <= 512
assert batch["targets"].shape[1] <= 620
dataloader = DataLoader(ConcatDataset(evals), collate_fn=collate_fn, batch_size=128)
for batch in dataloader:
assert batch["targets"].shape[1] <= 512
assert batch["targets"].shape[1] <= 620
test_collate_fn()
+54 -27
View File
@@ -1,14 +1,19 @@
import argparse
from distutils.util import strtobool
from functools import partial
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import bitsandbytes
import datasets
import torch
from efficiency_utils import fuse_gelu
from torch import nn
from torch.utils.data import DataLoader
from transformers import PreTrainedModel, Trainer, TrainingArguments
from transformers.trainer_pt_utils import IterableDatasetShard
from transformers.trainer_utils import seed_worker
from transformers.training_args import OptimizerNames
from transformers.utils import is_datasets_available
from utils import PerDatasetSampler, get_dataset, get_loss, get_metrics, get_model, get_tokenizer, read_yamls
@@ -34,10 +39,11 @@ class SFTTrainer(Trainer):
sampler: torch.utils.data.sampler.Sampler = None,
loss_function: str = "CrossEntropyLoss",
poly_eps: float = 1.0,
train_collate_fn: Callable = None,
**kwargs,
):
super().__init__(model, args, **kwargs)
self.train_collate_fn = train_collate_fn
# By default CrossEntropyLoss ignores padding_index -100, but just in case use our own loss_fct
self.loss_fct = get_loss(loss_function, poly_eps)
self.sampler = sampler
@@ -92,30 +98,51 @@ class SFTTrainer(Trainer):
return (loss, logits, labels)
def get_train_dataloader(self):
"""Inject custom data sampling behaviour into training loop"""
if self.sampler is None:
torch.utils.data.DataLoader(
self.train_dataset,
batch_size=self.args.per_device_train_batch_size,
shuffle=True,
collate_fn=self.data_collator,
)
else:
dataloader = torch.utils.data.DataLoader(
self.train_dataset,
batch_size=self.args.per_device_train_batch_size,
sampler=self.sampler,
collate_fn=self.data_collator,
)
if torch.cuda.device_count() <= 1:
return dataloader
else:
# Not strictly necessary to use accelerate, currently just
# ensures batches are padded to be divisible by # devices
from accelerate import Accelerator
"""
Inject custom data sampling behaviour into training loop
and use custom task mixing collate function : train_collate_fn
accelerator = Accelerator()
return accelerator.prepare(dataloader)
rewrite from:
https://github.com/huggingface/transformers/blob/67d074874d285e616393c65a0e670088e1b6b74a/src/transformers/trainer.py#L846
"""
data_collator = self.train_collate_fn
train_dataset = self.train_dataset
if is_datasets_available() and isinstance(train_dataset, datasets.Dataset):
train_dataset = self._remove_unused_columns(train_dataset, description="training")
if isinstance(train_dataset, torch.utils.data.IterableDataset):
# if we are using iterable dataset it means no weight sampling
# added for backward compat
if self.args.world_size > 1:
train_dataset = IterableDatasetShard(
train_dataset,
batch_size=self._train_batch_size,
drop_last=self.args.dataloader_drop_last,
num_processes=self.args.world_size,
process_index=self.args.process_index,
)
return DataLoader(
train_dataset,
batch_size=self.args.per_device_train_batch_size,
collate_fn=data_collator,
num_workers=self.args.dataloader_num_workers,
pin_memory=self.args.dataloader_pin_memory,
)
if self.sampler is None:
train_sampler = self._get_train_sampler()
else:
train_sampler = self.sampler
return DataLoader(
train_dataset,
batch_size=self._train_batch_size,
sampler=train_sampler,
collate_fn=data_collator,
drop_last=self.args.dataloader_drop_last,
num_workers=self.args.dataloader_num_workers,
pin_memory=self.args.dataloader_pin_memory,
worker_init_fn=seed_worker,
)
def _strtobool(x):
@@ -168,8 +195,7 @@ if __name__ == "__main__":
tokenizer = get_tokenizer(training_conf)
model = get_model(training_conf, tokenizer)
train, evals, collate_fn = get_dataset(training_conf, tokenizer)
train, evals, collate_fn, train_collate_fn = get_dataset(training_conf, tokenizer)
sampler = PerDatasetSampler.build_sampler_from_config(training_conf, train.datasets)
metrics, preprocess_fns = get_metrics(training_conf, tokenizer)
optimizer = OptimizerNames.ADAMW_BNB if training_conf.quantization else OptimizerNames.ADAMW_HF
@@ -222,6 +248,7 @@ if __name__ == "__main__":
model=model,
args=args,
sampler=sampler,
train_collate_fn=train_collate_fn,
loss_function=training_conf.loss_fn,
poly_eps=training_conf.poly_eps,
train_dataset=train,
+11 -4
View File
@@ -6,7 +6,7 @@ import evaluate
import transformers
import yaml
from custom_datasets import get_one_dataset
from custom_datasets.dialogue_collator import DialogueDataCollator
from custom_datasets.dialogue_collator import DialogueDataCollator, TrainDialogueDataCollator
from custom_datasets.qa_datasets import QA_SPECIAL_TOKENS
from losses import CrossEntropyLoss, PolyLoss
from models import freeze_top_n_layers, get_specific_model
@@ -126,7 +126,14 @@ def get_tokenizer(conf) -> transformers.AutoTokenizer:
if tokenizer_config.special_tokens:
if "GPT-JT" in conf.model_name:
tokenizer_config.special_tokens.pad_token = tokenizer.eos_token
tokenizer.add_special_tokens(tokenizer_config.special_tokens)
# SpecialTokens : latest in 4.25, 4.26
tokenizer.add_special_tokens(
{
"pad_token": tokenizer_config.special_tokens.pad_token,
"eos_token": tokenizer_config.special_tokens.eos_token,
"sep_token": tokenizer_config.special_tokens.sep_token,
}
)
additional_special_tokens = (
[]
@@ -231,8 +238,8 @@ def get_dataset(conf, tokenizer):
train = ConcatDataset(train_datasets)
collate_fn = DialogueDataCollator(tokenizer, max_length=conf.max_length)
return train, evals, collate_fn
train_collate_fn = TrainDialogueDataCollator(tokenizer, max_length=conf.max_length)
return train, evals, collate_fn, train_collate_fn
def get_loss(loss, poly_eps):
+1
View File
@@ -12,6 +12,7 @@ module.exports = {
"eu",
"fa",
"fr",
"gl",
"hu",
"it",
"ja",
+17
View File
@@ -85,6 +85,7 @@
"msw-storybook-addon": "^1.7.0",
"prettier": "2.8.1",
"prisma": "^4.7.1",
"ts-essentials": "^9.3.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
}
@@ -36509,6 +36510,15 @@
"node": ">=6.10"
}
},
"node_modules/ts-essentials": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.3.0.tgz",
"integrity": "sha512-XeiCboEyBG8UqXZtXl59bWEi4ZgOqRsogFDI6WDGIF1LmzbYiAkIwjkXN6zZWWl4re/lsOqMlYfe8KA0XiiEPw==",
"dev": true,
"peerDependencies": {
"typescript": ">=4.1.0"
}
},
"node_modules/ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
@@ -66035,6 +66045,13 @@
"resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz",
"integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="
},
"ts-essentials": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-9.3.0.tgz",
"integrity": "sha512-XeiCboEyBG8UqXZtXl59bWEi4ZgOqRsogFDI6WDGIF1LmzbYiAkIwjkXN6zZWWl4re/lsOqMlYfe8KA0XiiEPw==",
"dev": true,
"requires": {}
},
"ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+1
View File
@@ -103,6 +103,7 @@
"msw-storybook-addon": "^1.7.0",
"prettier": "2.8.1",
"prisma": "^4.7.1",
"ts-essentials": "^9.3.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
+10 -10
View File
@@ -1,24 +1,24 @@
{
"fails_task.question": "És una mala resposta, segons la tasca demanada?",
"hate_speech": "Incitació a l'odi",
"hate_speech.explanation": "El contingut és ofensiu o amenaçador i expressa un prejudici contra una característica protegida. El prejudici fa referència a les opinions preconcebudes que no es basen en la raó. Les característiques protegides inclouen gènere, ètnia, religió, orientació sexual i característiques semblants.",
"label_highlighted_flag_instruction": "Selecciona qualsevol que sigui adequat per al missatge ressaltat:",
"hate_speech.explanation": "El contingut és ofensiu o amenaçador i expressa un prejudici contra una característica protegida. Prejudici fa referència a les opinions preconcebudes que no es basen en la raó. Característiques protegides inclouen gènere, ètnia, religió, orientació sexual i característiques semblants.",
"label_highlighted_flag_instruction": "Selecciona'n qualsevol que sigui adequada per al missatge ressaltat:",
"label_highlighted_likert_instruction": "Qualifica el missatge ressaltat:",
"label_highlighted_yes_no_instruction": "Contesta la/les següent/s pregunta/es sobre el missatge ressaltat:",
"label_message_flag_instruction": "Selecciona qualsevol que sigui adequada per al missatge:",
"label_highlighted_yes_no_instruction": "Contesta les preguntes següents sobre el missatge ressaltat:",
"label_message_flag_instruction": "Selecciona'n qualsevol que sigui adequada per al missatge:",
"label_message_likert_instruction": "Qualifica el missatge:",
"label_message_yes_no_instruction": "Contesta la/les següent/s pregunta/es sobre el missatge:",
"lang_mismatch": "Idioma incorrecte",
"lang_mismatch.explanation": "No està escrit en l'idioma seleccionat actualment.",
"label_message_yes_no_instruction": "Contesta les preguntes següents sobre el missatge:",
"lang_mismatch": "No és {{language}}",
"lang_mismatch.explanation": "No està escrit en {{language}}.",
"moral_judgement": "Judici moral",
"moral_judgement.explanation": "Expressa un judici moral.",
"not_appropriate": "No apropiada",
"not_appropriate.explanation": "No apropiada per a un assistent de client.",
"pii": "Conté informació d'identificació personal (PII)",
"pii.explanation": "Conté informació personal com detalls de contacte personal, números d'identificació o detalls bancaris",
"pii": "Conté IPI",
"pii.explanation": "Conté Informació Personal Identificable, com detalls de contacte personal, números d'identificació personal o detalls bancaris",
"political_content": "Contingut polític",
"political_content.explanation": "Expressa una opinió política.",
"sexual_content": "Contingut sexual",
"sexual_content.explanation": "Conté contingut sexual.",
"spam.question": "És un missatge spam?"
"spam.question": "És un missatge brossa?"
}
+2 -2
View File
@@ -11,8 +11,8 @@
"rank": "Posició",
"reply": "Respostes",
"score": "Puntuació",
"top_5_contributors_today": "Top 5 contribuïdors avui",
"top_5_contributors_today": "Top 5 contribuïdors d'avui",
"user": "Usuari",
"view_all": "Veure tots",
"view_all": "Mostra'ls tots",
"weekly": "Setmanal"
}
+11 -11
View File
@@ -1,20 +1,20 @@
{
"copy_message_id": "Copiar ID del missatge",
"label_action": "Etiquetar",
"copy_message_id": "Copia l'ID del missatge",
"label_action": "Etiqueta",
"label_title": "Etiqueta",
"message": "Missatge",
"message_deleted": "Missatge esborrat",
"open_new_tab_action": "Obrir en una pestanya nova",
"message_deleted": "S'ha suprimit el missatge",
"open_new_tab_action": "Obre en una pestanya nova",
"parent": "Pare",
"reactions": "Reaccions",
"recent_messages": "Missatges recents",
"report_action": "Reportar",
"report_placeholder": "Per què cal revisar aquest missatge?",
"report_action": "Informa",
"report_placeholder": "Per què cal informar sobre aquest missatge?",
"report_title": "Informe",
"send_report": "Enviar",
"stop_tree": "Aturar arbre",
"submit_labels": "Enviar",
"tree_stopped": "Arbre aturat {{id}}",
"view_user": "Veure usuari",
"send_report": "Envia",
"stop_tree": "Atura l'arbre",
"submit_labels": "Envia",
"tree_stopped": "S'ha aturat l'arbre {{id}}",
"view_user": "Mostra l'usuari",
"your_recent_messages": "Els teus missatges recents"
}
+41 -41
View File
@@ -1,82 +1,82 @@
{
"available_task_count": "{{count}} tasques disponibles",
"classify_assistant_reply": {
"label": "Classificar resposta de l'assistent",
"label": "Classifica una resposta de l'assistent",
"desc": "Proporciona etiquetes per a una resposta de l'assistent.",
"overview": "Llegeix la conversa següent i contesta a continuació la pregunta sobre l'última resposta a la discussió."
"overview": "Llegeix la conversa següent i contesta la pregunta relacionada amb l'última resposta."
},
"classify_initial_prompt": {
"label": "Classificar instruccions inicials",
"desc": "Proporciona etiquetes per a unes instruccions.",
"overview": "Llegeix la següent entrada i contesta a continuació la pregunta sobre aquesta."
"label": "Classifica instruccions inicials",
"desc": "Proporciona etiquetes per a una instrucció.",
"overview": "Llegeix la instrucció següent i contesta la pregunta relacionada."
},
"classify_prompter_reply": {
"label": "Classificar la resposta de l'apuntador",
"desc": "Proporciona etiquetes per a una entrada.",
"overview": "Llegeix la conversa següent i contesta a continuació la pregunta sobre l'última resposta a la discussió."
"label": "Classifica la resposta de l'apuntador",
"desc": "Proporciona etiquetes per a una instrucció.",
"overview": "Llegeix la conversa següent i contesta la pregunta relacionada amb l'última resposta."
},
"create_initial_prompt": {
"label": "Crear instruccions inicials",
"desc": "Escriviu instruccions inicials per ajudar que Open Assistant intenti contestar missatges variats.",
"overview": "Crea un missatge inicial per enviar-lo a l'assistent",
"label": "Crea instruccions inicials",
"desc": "Escriu instruccions inicials perquè Open Assistant provi de contestar missatges variats.",
"overview": "Crea un missatge per a inciar conversa amb l'assistent",
"instruction": "Proporciona les instruccions inicials",
"response_placeholder": "Escriu les teves instruccions aquí..."
"response_placeholder": "Escriu les instruccions aquí..."
},
"default": {
"unchanged_title": "Sense canvis",
"unchanged_message": "Estàs segur que vols continuar?"
"unchanged_message": "Segur que vols continuar?"
},
"label_assistant_reply": {
"label": "Etiquetar resposta de l'assistent",
"desc": "Proporciona etiquetes per a unes instruccions.",
"overview": "Donada la discussió següent, proporciona etiquetes per a la darrera entrada."
"label": "Etiqueta la resposta de l'assistent",
"desc": "Proporciona etiquetes per a una instrucció.",
"overview": "Donada la conversa següent, proporciona etiquetes per a l'última entrada."
},
"label_initial_prompt": {
"label": "Etiquetar instruccions inicials",
"desc": "Proporciona etiquetes per a unes instruccions.",
"overview": "Proporciona etiquetes per a les instruccions següents"
"label": "Etiqueta instruccions inicials",
"desc": "Proporciona etiquetes per a una instrucció.",
"overview": "Proporciona etiquetes per a la instrucció següent"
},
"label_prompter_reply": {
"label": "Etiquetar resposta de l'apuntador",
"desc": "Proporciona etiquetes per a unes instruccions.",
"overview": "Donada la discussió següent, proporciona etiquetes per a la darrera entrada."
"label": "Etiqueta la resposta de l'apuntador",
"desc": "Proporciona etiquetes per a una instrucció.",
"overview": "Donada la conversa següent, proporciona etiquetes per a l'última entrada."
},
"random": {
"label": "Tindré sort",
"label": "Segur que tinc sort",
"desc": "Ajuda'ns a millorar Open Assistant començant amb una tasca aleatòria."
},
"rank_assistant_replies": {
"label": "Ordenar respostes de l'assistent",
"desc": "Puntua respostes donades per Open Assistant basant-te en la precisió i llegibilitat.",
"overview": "Donades les respostes següents de l'assistent, ordeneu-les de millor a pitjor, amb la primera sent la millor i l'última, la pitjor.",
"label": "Classifica les respostes de l'assistent",
"desc": "Puntua les respostes d'Open Assistant basant-te en la precisió i llegibilitat.",
"overview": "Donades les respostes següents de l'assistent, ordena-les de millor a pitjor, sent la primera la millor i l'última, la pitjor.",
"unchanged_title": "Ordre sense canviar",
"unchanged_message": "No heu canviat l'ordre de les respostes. Segur que vols continuar?"
"unchanged_message": "No has canviat l'ordre de les respostes. Segur que vols continuar?"
},
"rank_initial_prompts": {
"label": "Ordenar instruccions inicials",
"desc": "Puntua instruccions a Open Assistant basant-te en la precisió i llegibilitat.",
"overview": "Donades les següents instruccions inicials, ordeneu-les de millor a pitjor, amb la primera sent la millor i l'última, la pitjor.",
"label": "Classifica les instruccions inicials",
"desc": "Puntua les instruccions donades a Open Assistant basant-te en la precisió i llegibilitat.",
"overview": "Donades les instruccions inicials següents, ordena-les de millor a pitjor, sent la primera la millor i l'última, la pitjor.",
"unchanged_title": "Ordre sense canviar",
"unchanged_message": "No heu canviat l'ordre de les entrades. Segur que vols continuar?"
"unchanged_message": "No has canviat l'ordre de les entrades. Segur que vols continuar?"
},
"rank_user_replies": {
"label": "Ordenar respostes d'usuaris",
"desc": "Ajuda Open Assistant a millorar les seves respostes a converses amb altres usuaris.",
"overview": "Donades les respostes següents d'usuaris, ordeneu-les de millor a pitjor, amb la primera sent la millor i l'última, la pitjor.",
"label": "Classifica les respostes dels usuaris",
"desc": "Ajuda Open Assistant a millorar les seves respostes en converses amb altres usuaris.",
"overview": "Donades les respostes següents d'usuaris, ordena-les de millor a pitjor, sent la primera la millor i l'última, la pitjor.",
"unchanged_title": "Ordre sense canviar",
"unchanged_message": "No heu canviat l'ordre de les respostes. Segur que vols continuar?"
"unchanged_message": "No has canviat l'ordre de les respostes. Segur que vols continuar?"
},
"reply_as_assistant": {
"label": "Contestar com a assistent",
"desc": "Ajuda Open Assistant a millorar les seves respostes a converses amb altres usuaris.",
"label": "Contesta com a assistent",
"desc": "Ajuda Open Assistant a millorar les seves respostes en converses amb altres usuaris.",
"overview": "Donada la conversa següent, proporciona una resposta adequada",
"response_placeholder": "Escriu la teva resposta aquí..."
"response_placeholder": "Escriu la resposta aquí..."
},
"reply_as_user": {
"label": "Contestar com a usuari",
"desc": "Parlar amb Open Assistant i intentar millorar les respostes mentre interacciones amb ell.",
"label": "Contesta com a usuari",
"desc": "Parla amb Open Assistant i intenta millorar les respostes mentre interacciones amb ell.",
"overview": "Donada la conversa següent, proporciona una resposta adequada",
"instruction": "Proporciona la resposta de l'usuari",
"response_placeholder": "Escriu la teva resposta aquí..."
"response_placeholder": "Escriu la resposta aquí..."
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"accept": "Acceptar",
"content": "Per continuar utilitzant Open Assistant, primer has d'acceptar les nostres Condicions del servei.",
"decline": "Rebutjar",
"accept": "Accepta",
"content": "Per a continuar utilitzant Open Assistant, primer has d'acceptar les Condicions del servei.",
"decline": "Rebutja",
"title": "Condicions del servei d'Open Assistant"
}
+7
View File
@@ -20,13 +20,20 @@
"messages": "Messages",
"more_information": "More Information",
"no": "No",
"output": "Output",
"parameters": "Parameters",
"privacy_policy": "Privacy Policy",
"prompt": "Prompt",
"report_a_bug": "Report a Bug",
"review": "Review",
"sign_in": "Sign In",
"sign_out": "Sign Out",
"skip": "Skip",
"status": "Status",
"status_dashboard": "Status Dashboard",
"send": "Send",
"submit": "Submit",
"submit_your_answer": "Submit your answer",
"success": "Success",
"terms_of_service": "Terms of Service",
"title": "Open Assistant",
+56 -1
View File
@@ -1,7 +1,35 @@
{
"accurate": "Accurate",
"apolitical": "Apolitical",
"appropriate": "Appropriate",
"boring": "Boring",
"clean": "Clean",
"contains_pii": "Contains PII",
"contains_pii.one_desc": "Contains personally identifying information",
"creative": "Creative",
"creative.one_desc": "Expresses creativity in responding to the task",
"fails_task": "Fails Task",
"fails_task.one_desc": "Fails to follow the correct instruction / task",
"fails_task.question": "Is it a bad reply, as an answer to the prompt task?",
"follows_instructions": "Follows Instructions",
"harmful": "Harmful",
"harmful.one_desc.line_1": "Content likely to cause excessive harm not justifiable in the context",
"harmful.one_desc.line_2": "Harm refers to physical or mental damage or injury to someone or something. Excessive refers to a reasonable threshold of harm in the context, for instance damaging skin is not excessive in the context of surgery.",
"harmless": "Harmless",
"hate_speech": "Hate Speech",
"hate_speech.explanation": "Content is abusive or threatening and expresses prejudice against a protected characteristic. Prejudice refers to preconceived views not based on reason. Protected characteristics include gender, ethnicity, religion, sexual orientation, and similar characteristics.",
"hateful": "Hateful",
"hateful.one_desc.line_1": "Content is abusive or threatening and expresses prejudice against a protected characteristic",
"hateful.one_desc.line_2": "Prejudice refers to preconceived views not based on reason. Protected characteristics include gender, ethnicity, religion, sexual orientation, and similar characteristics.",
"helpful": "Helpful",
"helpful.one_desc": "Completes the task to a high standard",
"high_quality": "High Quality",
"humorous": "Humorous",
"humorous.one_desc": "Contains humorous content including sarcasm",
"inappropriate": "Inappropriate",
"inappropriate.one_desc": "Inappropriate for customer assistant",
"judgemental": "Judgemental",
"judgemental.one_desc": "Expresses moral judgement",
"label_highlighted_flag_instruction": "Select any that apply to the highlighted message:",
"label_highlighted_likert_instruction": "Rate the highlighted message:",
"label_highlighted_yes_no_instruction": "Answer the following question(s) about the highlighted message:",
@@ -10,15 +38,42 @@
"label_message_yes_no_instruction": "Answer the following question(s) about the message:",
"lang_mismatch": "Not {{language}}",
"lang_mismatch.explanation": "Not written in {{language}}.",
"low_quality": "Low Quality",
"misleading": "Misleading",
"misleading.one_desc": "Contains text which is incorrect or misleading",
"moral_judgement": "Judges Morality",
"moral_judgement.explanation": "Expresses moral judgement.",
"non_judgemental": "Non-judgemental",
"non_sexual": "Non-sexual",
"not_spam": "Not spam",
"not_spam.explanation": "Suitable for training Open Assistant.",
"not_appropriate": "Not Appropriate",
"not_appropriate.explanation": "Inappropriate for a customer assistant.",
"ordinary": "Ordinary",
"pii": "Contains PII",
"pii.explanation": "Contains personally identifying information. Examples include personal contact details, license and other identity numbers and banking details.",
"polite": "Polite",
"political": "Political",
"political_content": "Political",
"political_content.explanation": "Expresses political views.",
"political.one_desc": "Expresses political views",
"rude": "Rude",
"rude.one_desc": "Contains rude, abusive, profane or insulting content",
"safe": "Safe",
"serious": "Serious",
"spam": "Spam",
"spam.question": "Is the message spam?",
"spam.one_desc.line_1": "Seems to be intentionally low-quality or irrelevant",
"spam.one_desc.line_2": "We consider the following unwanted content as spam: trolling, intentional undermining of our purpose, illegal material, material that violates our code of conduct, and other things that are inappropriate for our dataset. We collect these under the common heading of \"spam\".",
"spam.one_desc.line_3": "This is not an assessment of whether this message is the best possible answer. Especially for prompts or user-replies, we very much want to retain all kinds of responses in the dataset, so that the assistant can learn to reply appropriately.",
"spam.one_desc.line_4": "Please mark this text as spam only if it is clearly unsuited to be part of our dataset, as outlined above, and try not to make any subjective value-judgments beyond that.",
"sexual": "Sexual",
"sexual_content": "Sexual Content",
"sexual_content.explanation": "Contains sexual content.",
"spam.question": "Is the message spam?"
"sexual.one_desc": "Contains sexual content",
"threatening": "Threatening",
"threatening.one_desc": "Contains a threat against a person or persons",
"unhelpful": "Unhelpful",
"violent": "Violent",
"violent.one_desc": "Encourages or fails to discourage violence/abuse/terrorism/self-harm"
}
@@ -12,6 +12,8 @@
"month": "Month",
"monthly": "Monthly",
"next": "Next",
"no_email": "(No email)",
"no_username": "(No username)",
"overall": "Overall",
"previous": "Previous",
"prompt": "Prompts",
@@ -25,6 +27,7 @@
"top_5_contributors_today": "Top 5 Contributors Today",
"total": "Total",
"user": "User",
"username": "Username",
"view_all": "View all",
"week": "Week",
"weekly": "Weekly",
+4 -3
View File
@@ -1,4 +1,6 @@
{
"any_feedback_on_this_task": "Any feedback on this task?",
"available_task_count": "{{count}} tasks available",
"default": {
"unchanged_title": "No changes",
"unchanged_message": "Are you sure you would like to continue?"
@@ -78,7 +80,6 @@
"desc": "Provide labels for a prompt.",
"overview": "Read the following conversation and then answer the question about the last reply in the discussion."
},
"available_task_count": "{{count}} tasks available",
"writing_wrong_langauge_a_b": "You appear to be writing in {{detected_lang}} but this will be submitted as {{submit_lang}}.",
"submitted_as": "This will be submitted as {{submit_lang}}"
"submitted_as": "This will be submitted as {{submit_lang}}",
"writing_wrong_langauge_a_b": "You appear to be writing in {{detected_lang}} but this will be submitted as {{submit_lang}}."
}
+7
View File
@@ -20,13 +20,20 @@
"messages": "Mensajes",
"more_information": "Más información",
"no": "No",
"output": "Salida",
"parameters": "Parámetros",
"privacy_policy": "Política de privacidad",
"prompt": "Indicación",
"report_a_bug": "Informar de un error",
"review": "Revisar",
"sign_in": "Iniciar sesión",
"sign_out": "Cerrar sesión",
"skip": "Saltar",
"status": "Estado",
"status_dashboard": "Tablón de estado",
"send": "Enviar",
"submit": "Enviar",
"submit_your_answer": "Enviar tu respuesta",
"success": "Éxito",
"terms_of_service": "Términos de servicio",
"title": "Open Assistant",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"blurb": "Creemos que podemos crear una revolución.",
"blurb1": "De la misma manera que Stable Diffusion ayudó al mundo a crear arte e imágenes de maneras nuevas, queremos mejorar el mundo proporcionando una IA conversacional asombrosa",
"blurb1": "De la misma forma que Stable Diffusion ayudó al mundo a crear arte e imágenes de nuevas maneras, queremos mejorar el mundo proporcionando una IA conversacional asombrosa.",
"description": "IA conversacional para todos. Un proyecto de código abierto para crear un GPT LLM preparado para chatear administrado por LAION y colaboradores de todo el mundo.",
"faq_items": {
"q0": "¿Cómo de avanzado está el proyecto?",
@@ -16,7 +16,7 @@
"q5": "¿Qué hardware será necesario para ejecutar los modelos?",
"a5": "Habrá versiones que se podrán ejecutar en hardware de consumo."
},
"faq_title": "Preguntas frequentes",
"faq_title": "Preguntas frecuentes",
"join_us_description": "Todos los proyectos de código abierto comienzan con personas como tú. El código abierto es la creencia de que si colaboramos juntos, podemos regalar nuestro conocimiento y tecnología al mundo en beneficio de la humanidad. ¿Te apuntas? Encuéntranos aquí:",
"join_us_title": "Únete a nosotros",
"subtitle": "IA conversacional para todos."
+59 -4
View File
@@ -1,24 +1,79 @@
{
"accurate": "Preciso",
"apolitical": "Apolítico",
"appropriate": "Apropriado",
"boring": "Aburrido",
"clean": "Limpio",
"contains_pii": "Contiene PII",
"contains_pii.one_desc": "Contiene información de identificación personal (PII)",
"creative": "Creativo",
"creative.one_desc": "Expresa creatividad en la respuesta a la tarea",
"fails_task": "Falla en la tarea",
"fails_task.one_desc": "Falla en seguir las instrucciones/tareas correctas",
"fails_task.question": "¿Es una mala respuesta, según la tarea pedida?",
"follows_instructions": "Sigue las instrucciones",
"harmful": "Dañino",
"harmful.one_desc.line_1": "Contenido que probablemente cause daño excesivo no justificable por el contexto",
"harmful.one_desc.line_2": "Daño se refiere a hacer daño o causar heridas a alguien o algo. Excesivo se refiere a un nivel de daño razonable en el contexto, por ejemplo, dañar la piel no es excesivo en el contexto de una operación.",
"harmless": "Inofensivo",
"hate_speech": "Incitación al odio",
"hate_speech.explanation": "El contenido es ofensivo o amenazante y expresa un prejuicio contra una característica protegida. El prejuicio se refiere a las opiniones preconcebidas que no se basan en la razón. Las características protegidas incluyen género, etnia, religión, orientación sexual y características similares.",
"hateful": "Odiosa",
"hateful.one_desc.line_1": "El contenido es abusivo o amenazante y expresa prejuicio contra una característica protegida",
"hateful.one_desc.line_2": "El prejuicio se refiere a puntos de vista preconcebidos que no se basan en la razón. Las características protegidas incluyen género, etnia, religión, orientación sexual y características similares.",
"helpful": "Útil",
"helpful.one_desc": "Completa la tarea con un alto nivel",
"high_quality": "Alta calidad",
"humorous": "Humorístico",
"humorous.one_desc": "Contiene contenido humorístico, incluyendo sarcasmo.",
"inappropriate": "Inapropiado",
"inappropriate.one_desc": "Inapropiado para asistente de cliente",
"judgemental": "Moralista",
"judgemental.one_desc": "Expresa un juicio moral",
"label_highlighted_flag_instruction": "Selecciona cualquiera que sea adecuada para el mensaje resaltado:",
"label_highlighted_likert_instruction": "Califica el mensaje resaltado:",
"label_highlighted_yes_no_instruction": "Contesta la(s) siguiente(s) pregunta(s) sobre el mensaje resaltado:",
"label_message_flag_instruction": "Selecciona cualquiera que sea adecuada para el mensaje:",
"label_message_likert_instruction": "Califica el mensaje:",
"label_message_yes_no_instruction": "Contesta la(s) siguiente(s) pregunta(s) sobre el mensaje:",
"lang_mismatch": "Idioma incorrecto",
"lang_mismatch.explanation": "No está escrito en el idioma seleccionado actualmente.",
"lang_mismatch": "No está en {{language}}",
"lang_mismatch.explanation": "No está escrito en {{language}}.",
"low_quality": "Baja calidad",
"misleading": "Engañoso",
"misleading.one_desc": "Contiene texto incorrecto o engañoso",
"moral_judgement": "Juicio moral",
"moral_judgement.explanation": "Expresa un juicio moral.",
"non_judgemental": "No crítico",
"non_sexual": "No es sexual",
"not_spam": "No es spam",
"not_spam.explanation": "Apto para entrenar Open Assistant.",
"not_appropriate": "No apropiada",
"not_appropriate.explanation": "No apropiada para un asistente de cliente.",
"not_appropriate.explanation": "No apropiada para un asistente.",
"ordinary": "Corriente",
"pii": "Contiene información de identificación personal (PII)",
"pii.explanation": "Contiene información personal como detalles de contacto personal, números de identificación o detalles bancarios",
"polite": "Educado",
"political": "Político",
"political_content": "Contenido político",
"political_content.explanation": "Expresa una opinión política.",
"political.one_desc": "Expresa opiniones políticas",
"rude": "Maleducado",
"rude.one_desc": "Contiene contenido grosero, abusivo, soez o insultante.",
"safe": "Seguro",
"serious": "Serio",
"spam": "Spam",
"spam.question": "¿Es el mensaje spam?",
"spam.one_desc.line_1": "Parece ser intencionalmente de baja calidad o irrelevante",
"spam.one_desc.line_2": "Consideramos el siguiente contenido no deseado como spam: trolear, socavación intencionada de nuestro propósito, material ilegal, material que viola nuestro código de conducta y otras cosas que son inapropiadas para nuestro conjunto de datos. Recopilamos todo esto bajo el encabezado común de \"spam\".",
"spam.one_desc.line_3": "Esta no es una evaluación de si este mensaje es la mejor respuesta posible. Especialmente para las instrucciones o las respuestas de los usuarios, queremos conservar todo tipo de respuestas en el conjunto de datos, para que el asistente pueda aprender a responder adecuadamente.",
"spam.one_desc.line_4": "Marque este texto como spam solo si es claramente inadecuado para ser parte de nuestro conjunto de datos, como se describe anteriormente, y trate de no hacer ningún juicio de valor subjetivo más allá de eso.",
"sexual": "Sexual",
"sexual_content": "Contenido sexual",
"sexual_content.explanation": "Contiene contenido sexual.",
"spam.question": "¿Es el mensaje spam?"
"sexual.one_desc": "Contiene contenido sexual",
"threatening": "Amenazante",
"threatening.one_desc": "Contiene una amenaza contra una o varias personas",
"unhelpful": "De poca ayuda",
"violent": "Violento",
"violent.one_desc": "Fomenta o no desalienta la violencia, el abuso, el terrorismo o las autolesiones"
}
@@ -12,6 +12,8 @@
"month": "Mes",
"monthly": "Mensual",
"next": "Siguiente",
"no_email": "(Sin dirección de correo)",
"no_username": "(Sin nombre de usuario)",
"overall": "Global",
"previous": "Anterior",
"prompt": "Indicaciones",
@@ -25,6 +27,7 @@
"top_5_contributors_today": "5 mayores contribuidores hoy",
"total": "Total",
"user": "Usuario",
"username": "Nombre de usuario",
"view_all": "Ver todos",
"week": "Semana",
"weekly": "Semanal",
+3 -2
View File
@@ -1,4 +1,5 @@
{
"any_feedback_on_this_task": "¿Alguna realimentación sobre esta tarea?",
"available_task_count": "{{count}} tareas disponibles",
"classify_assistant_reply": {
"label": "Clasificar respuesta del asistente",
@@ -79,6 +80,6 @@
"instruction": "Proporciona la respuesta del usuario",
"response_placeholder": "Escribe tu respuesta aquí..."
},
"writing_wrong_langauge_a_b": "Parece que estás escribiendo en {{detected_lang}} pero esto se envia como {{submit_lang}}.",
"submitted_as": "Esto será enviado como {{submit_lang}}"
"submitted_as": "Esto se enviado como {{submit_lang}}",
"writing_wrong_langauge_a_b": "Parece que estás escribiendo en {{detected_lang}} pero esto se envia como {{submit_lang}}."
}
+5 -5
View File
@@ -2,21 +2,21 @@
"available_task_count": "{{count}} zeregin eskuragarri",
"classify_assistant_reply": {
"desc": "Jarri etiketak instrukzio bati.",
"label": "Sailifikatu Laguntzailea eginbidearen erantzuna",
"label": "Sailkatu asistentearen erantzuna",
"overview": "Irakurri hurrengo elkarrizketa eta erantzun elkarrizketako azken erantzunari buruzko galdera."
},
"classify_initial_prompt": {
"desc": "Jarri etiketak instrukzio bati.",
"label": "Salifikatu hasierako instrukzioa",
"label": "Sailkatu hasierako instrukzioa",
"overview": "Irakurri hurrengo instrukzioa eta erantzun horri buruzko galdera."
},
"classify_prompter_reply": {
"desc": "Jarri etiketak instrukzio bati.",
"label": "Sailifikatu galdetzailearen erantzuna",
"label": "Sailkatu erabiltzailearen erantzuna",
"overview": "Irakurri hurrengo elkarrizketa eta erantzun elkarrizketako azken erantzunari buruzko galdera."
},
"create_initial_prompt": {
"desc": "Idatzi hasierako instrukzioak Open Assistant-i era askotako mezuak erantzuten saia dadin. (sartu loterian)",
"desc": "Idatzi hasierako instrukzioak Open Assistant era askotako mezuak erantzuten saia dadin.",
"instruction": "Eman hasierako instrukzioak",
"label": "Sortu hasierako instrukzioak",
"overview": "Sortu hasierako mezu bat asistenteari bidaltzeko",
@@ -79,6 +79,6 @@
"overview": "Ondoko elkarrizketa ikusita, eman erantzun egokia",
"response_placeholder": "Idatzi zure erantzuna hemen..."
},
"submitted_as": "Hau {{submit_lang}} hizkuntzan bidaliko da ",
"submitted_as": "Hau {{submit_lang}} hizkuntzan bidaliko da",
"writing_wrong_langauge_a_b": "Ematen du {{detected_lang}} hizkuntzan baina hau {{submit_lang}} hizkuntzan bidaliko da."
}
+38
View File
@@ -0,0 +1,38 @@
{
"about": "Acerca",
"account_settings": "Conta",
"admin_dashboard": "Panel de administración",
"connect": "Conectar",
"conversational": "IA conversacional para todos",
"copied": "Copiado",
"dark_mode": "Modo escuro",
"dashboard_home": "Panel principal",
"dashboard": "Panel",
"delete": "Eliminar",
"discord": "Discord",
"docs": "Documentación",
"github": "GitHub",
"leaderboard": "Táboa de líderes",
"legal": "Legal",
"light_mode": "Modo claro",
"loading": "Cargando...",
"messages_dashboard": "Panel de mensaxes",
"messages": "Mensaxes",
"more_information": "Máis información",
"no": "Non",
"parameters": "Parámetros",
"privacy_policy": "Política de privacidade",
"report_a_bug": "Informar dun erro",
"sign_in": "Iniciar sesión",
"sign_out": "Pechar sesión",
"status": "Estado",
"status_dashboard": "Panel de estado",
"success": "Éxito",
"terms_of_service": "Termos de servizo",
"title": "Open Assistant",
"trollboard": "Trollboard",
"user_leaderboard": "Táboa de usuarios",
"users_dashboard": "Panel de usuario",
"users": "Usuarios",
"yes": "Si"
}
+8
View File
@@ -0,0 +1,8 @@
{
"grab_a_task": "Colle unha tarefa!",
"create": "Crear",
"evaluate": "Avaliar",
"label": "Etiqueta",
"dashboard": "Panel",
"go": "Ir"
}
+23
View File
@@ -0,0 +1,23 @@
{
"blurb": "Cremos que podemos crear unha revolución.",
"blurb1": "Do mesmo xeito que Stable Diffusion axudou ao mundo a facer arte e imaxes de novas maneiras, queremos mellorar o mundo proporcionando unha incríbel IA conversacional.",
"description": "AI conversacional para todos. Un proxecto de código aberto para crear un chat habilitado con GPT LLM dirixido por LAION e colaboradores de todo o mundo.",
"faq_items": {
"q0": "Como de avanzado está o proxecto?",
"a0": "Estamos nas primeiras etapas de desenvolvemento, traballando dende a investigación establecida na aplicación de RLHF (aprendizaxe por reforzo con retroalimentación humana) ata grandes modelos de linguaxe.",
"q1": "Quen está detrás de Open Assistant?",
"a1": "Open Assistant é un proxecto organizado por LAION e individuos de todo o mundo interesados en achegar esta tecnoloxía á xente.",
"q2": "Que licencia emprega Open Assistant?",
"a2": "O código e os modelos están licenciados baixo a licencia Apache 2.0.",
"q3": "Serán publicados os datos de adestramento?",
"a3": "Si, baixo a licencia CC BY 4.0.",
"q4": "Será Open Assistant libre?",
"a4": "Si, Open Assistant será libre de usar e modificar.",
"q5": "Que hardware será necesario para executar os modelos?",
"a5": "Haberá versións que se poderán executar en hardware de consumo."
},
"faq_title": "Preguntas frecuentes",
"join_us_description": "Tódolos proxectos de código aberto comezan con xente coma ti. O código aberto é a crenza de que se colaboramos podemos agasallar o noso coñecemento e tecnoloxía ao mundo en beneficio da humanidade. Estás dentro? Atópanos aquí:",
"join_us_title": "Únete a nós",
"subtitle": "AI conversacional para todo o mundo."
}
+24
View File
@@ -0,0 +1,24 @@
{
"fails_task.question": "É unha mala reposta, segundo a tarefa pedida?",
"hate_speech": "Incitación ao odio",
"hate_speech.explanation": "Contido abusivo ou ameazante que expresa prexuicios contra unha característica protexida. O prexuízo refírese a opinións preconcebidas que non se basean na razón. As características protexidas inclúen o xénero, a etnia, a relixión, a orientación sexual e similares.",
"label_highlighted_flag_instruction": "Elixe calquera que sexa adoitada para a mensaxe destacada:",
"label_highlighted_likert_instruction": "Valora a mensaxe destacada:",
"label_highlighted_yes_no_instruction": "Responde á(s) seguinte(s) pregunta(s) sobre a mensaxe destacada:",
"label_message_flag_instruction": "Elixe calquera que sexa adoitada para a mensaxe:",
"label_message_likert_instruction": "Califica a mensaxe:",
"label_message_yes_no_instruction": "Responde á(s) seguinte(s) pregunta(s) sobre a mensaxe:",
"lang_mismatch": "Non é {{language}}",
"lang_mismatch.explanation": "Non está escrito en {{language}}.",
"moral_judgement": "Xuízo moral",
"moral_judgement.explanation": "Expresa xuízo moral.",
"not_appropriate": "Non apropiado",
"not_appropriate.explanation": "Non apropiado para un asistente.",
"pii": "Contén información de identificación persoal (PII)",
"pii.explanation": "Contén información de identificación persoal. Algúns exemplos son: detalles de contacto persoais, números de licenza e de identidade e detalles bancarios.",
"political_content": "Contido político",
"political_content.explanation": "Expresa opinións políticas.",
"sexual_content": "Contido sexual",
"sexual_content.explanation": "Contén contido sexual.",
"spam.question": "É spam?"
}
@@ -0,0 +1,33 @@
{
"accepted": "↪ Aceptadas",
"accepted_prompts": "Indicacións aceptadas",
"daily": "Diario",
"day": "Día",
"good_rankings": "Boas calificacións",
"label": "Etiquetas",
"labels_full": "Etiquetas (completas)",
"labels_simple": "Etiquetas (sinxelas)",
"last_updated_at": "Última actualización: {{val, datetime}}",
"leaderboard": "Táboa de líderes",
"month": "Mes",
"monthly": "Mensual",
"next": "Seguinte",
"overall": "Global",
"previous": "Anterior",
"prompt": "Indicacións",
"rank": "Posición",
"rankings": "Clasificacións",
"replies_assistant": "Resposta como asistente",
"replies_prompter": "Resposta como apuntador",
"reply": "Respostas",
"reply_ranked_1": "Resposta calificada como 1ª",
"score": "Puntuación",
"top_5_contributors_today": "Os 5 principais colaboradores de hoxe",
"total": "Total",
"user": "Usuario",
"view_all": "Ver todo",
"week": "Semana",
"weekly": "Semanal",
"your_account": "A túa conta",
"your_stats": "As túas estatísticas"
}
+21
View File
@@ -0,0 +1,21 @@
{
"copy_message_id": "Copiar ID da mensaxe",
"copy_message_link": "Copiar ligazón da mensaxe",
"label_action": "Etiquetar",
"label_title": "Etiqueta",
"message_deleted": "Mensaxe eliminada",
"message": "Mensaxe",
"open_new_tab_action": "Abrir nunha nova lapela",
"parent": "Pai",
"reactions": "Reaccións",
"recent_messages": "Mensaxes recentes en {{language}}",
"report_action": "Informar",
"report_placeholder": "Porque se debería informar desta mensaxe?",
"report_title": "Informe",
"send_report": "Enviar informe",
"stop_tree": "Parar árbore",
"submit_labels": "Enviar",
"tree_stopped": "Árbore parado {{id}}",
"view_user": "Ver usuario",
"your_recent_messages": "As túas mensaxes recentes"
}
+84
View File
@@ -0,0 +1,84 @@
{
"default": {
"unchanged_title": "Sen cambios",
"unchanged_message": "Estás seguro de que desexas continuar?"
},
"random": {
"label": "Vou ter sorte",
"desc": "Axúdanos a mellorar Open Assistant comezando unha tarefa aleatoria."
},
"create_initial_prompt": {
"label": "Crea Indicacións Iniciais",
"desc": "Escribe as indicacións iniciais para axudar a Open Assistant a responder a diversas mensaxes.",
"overview": "Crea unha mensaxe inicial para enviar ao asistente",
"instruction": "Proporciona as indicacións iniciais",
"response_placeholder": "Escribe a túa indicación aquí..."
},
"reply_as_user": {
"label": "Responder como usuario",
"desc": "Fala con Open Assistant e axuda a mellorar as súas respostas mentres interactúas con él.",
"overview": "Dada a seguinte conversa, proporciona unha resposta axeitada",
"instruction": "Proporciona a resposta de usuario",
"response_placeholder": "Escribe a túa resposta aquí..."
},
"reply_as_assistant": {
"label": "Responder como asistente",
"desc": "Axuda a Open Assistant a mellorar as súas respostas a conversas con outros usuarios.",
"overview": "Dada a seguinte conversa, proporciona unha resposta axeitada",
"response_placeholder": "Escribe a túa resposta aquí..."
},
"rank_user_replies": {
"label": "Califica as respostas dos usuarios",
"desc": "Axuda a Open Assistant a mellorar as súas respostas a conversas con outros usuarios.",
"overview": "Dadas as seguintes respostas de usuarios, ordéaas da mellor á peor, sendo a mellor a primeira e a peor a última.",
"unchanged_title": "Orde sen cambios",
"unchanged_message": "Non cambiaches a orde das respostas. Estás seguro de que desexas continuar?"
},
"rank_assistant_replies": {
"label": "Califica as respostas do asistente",
"desc": "Califica indicacións dadas por Open Assistant en función da súa precisión e lexibilidade.",
"overview": "Dadas as seguintes respostas do asistente, ordéaas da mellor á peor, sendo a mellor a primeira e a peor a última.",
"unchanged_title": "Orde sen cambios",
"unchanged_message": "Non cambiaches a orde das respostas. Estás seguro de que desexas continuar?"
},
"rank_initial_prompts": {
"label": "Califica as indicacións iniciais",
"desc": "Califica indicacións dadas por Open Assistant en función da súa precisión e lexibilidade.",
"overview": "Dadas as seguintes indicacións iniciais, ordéaas da mellor á peor, sendo a mellor a primeira e a peor a última.",
"unchanged_title": "Orde sen cambios",
"unchanged_message": "Non cambiaches a orde das respostas. Estás seguro de que desexas continuar?"
},
"label_initial_prompt": {
"label": "Etiqueta de Indicacións Iniciais",
"desc": "Proporciona etiquetas para unha indicación.",
"overview": "Proporciona etiquetas para a seguinte indicación"
},
"label_prompter_reply": {
"label": "Etiqueta de Resposta do Apuntador",
"desc": "Proporciona etiquetas para unha indicación.",
"overview": "Dada a seguinte conversa, proporciona etiquetas para a indicación final."
},
"label_assistant_reply": {
"label": "Etiqueta de Resposta do Asistente",
"desc": "Proporciona etiquetas para unha indicación.",
"overview": "Dada a seguinte conversa, proporciona etiquetas para a indicación final."
},
"classify_initial_prompt": {
"label": "Clasifica as Indicación Inicial",
"desc": "Proporciona etiquetas para unha indicación.",
"overview": "Le a seguinte indicación e responde á pregunta acerca dela."
},
"classify_prompter_reply": {
"label": "Clasifica as Repostas do Apuntador",
"desc": "Proporciona etiquetas para unha indicación.",
"overview": "Le a seguinte conversa e responde á pregunta do final."
},
"classify_assistant_reply": {
"label": "Clasifica as Repostas do Asistente",
"desc": "Proporciona etiquetas para unha indicación.",
"overview": "Le a seguinte conversa e responde á pregunta do final."
},
"available_task_count": "{{count}} tarefas dispoñibles",
"writing_wrong_langauge_a_b": "Parece que estás a escribir en {{detected_lang}} pero esto vai ser enviado en como {{submit_lang}}.",
"submitted_as": "Esto vai ser enviado como {{submit_lang}}"
}
+6
View File
@@ -0,0 +1,6 @@
{
"title": "Termos de Servicio para Open Assistant",
"content": "Para continuar usando Open Assistant, tes que aceptar os nosos Termos de Servicio primeiro.",
"accept": "Aceptar",
"decline": "Rexeitar"
}
+6 -4
View File
@@ -11,6 +11,7 @@ import {
Textarea,
useDisclosure,
} from "@chakra-ui/react";
import { useTranslation } from "next-i18next";
import { useState } from "react";
interface SkipButtonProps extends ButtonProps {
@@ -18,6 +19,7 @@ interface SkipButtonProps extends ButtonProps {
}
export const SkipButton = ({ onSkip, ...props }: SkipButtonProps) => {
const { t } = useTranslation(["common", "tasks"]);
const { isOpen, onOpen: showModal, onClose: closeModal } = useDisclosure();
const [value, setValue] = useState("");
@@ -30,25 +32,25 @@ export const SkipButton = ({ onSkip, ...props }: SkipButtonProps) => {
return (
<>
<Button size="lg" variant="outline" onClick={showModal} {...props}>
Skip
{t("skip")}
</Button>
<Modal isOpen={isOpen} onClose={closeModal}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Skip</ModalHeader>
<ModalHeader>{t("skip")}</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Textarea
value={value}
onChange={(e) => setValue(e.target.value)}
resize="none"
placeholder="Any feedback on this task?"
placeholder={t("tasks:any_feedback_on_this_task")}
/>
</ModalBody>
<ModalFooter>
<Button colorScheme="blue" mr={3} onClick={onSubmit}>
Send
{t("send")}
</Button>
</ModalFooter>
</ModalContent>
+6 -3
View File
@@ -1,8 +1,11 @@
import { LucideIcon } from "lucide-react";
import { LucideIcon, LucideProps } from "lucide-react";
import { forwardRef } from "react";
export const Discord: LucideIcon = ({ size = 24, ...rest }) => {
// eslint-disable-next-line react/display-name
export const Discord: LucideIcon = forwardRef<SVGSVGElement, LucideProps>(function ({ size = 24, ...rest }, ref) {
return (
<svg
ref={ref}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 127.14 96.36"
fill="currentColor"
@@ -13,4 +16,4 @@ export const Discord: LucideIcon = ({ size = 24, ...rest }) => {
<path d="M107.7 8.07A105.15 105.15 0 0 0 81.47 0a72.06 72.06 0 0 0-3.36 6.83 97.68 97.68 0 0 0-29.11 0A72.37 72.37 0 0 0 45.64 0a105.89 105.89 0 0 0-26.25 8.09C2.79 32.65-1.71 56.6.54 80.21a105.73 105.73 0 0 0 32.17 16.15 77.7 77.7 0 0 0 6.89-11.11 68.42 68.42 0 0 1-10.85-5.18c.91-.66 1.8-1.34 2.66-2a75.57 75.57 0 0 0 64.32 0c.87.71 1.76 1.39 2.66 2a68.68 68.68 0 0 1-10.87 5.19 77 77 0 0 0 6.89 11.1 105.25 105.25 0 0 0 32.19-16.14c2.64-27.38-4.51-51.11-18.9-72.15ZM42.45 65.69C36.18 65.69 31 60 31 53s5-12.74 11.43-12.74S54 46 53.89 53s-5.05 12.69-11.44 12.69Zm42.24 0C78.41 65.69 73.25 60 73.25 53s5-12.74 11.44-12.74S96.23 46 96.12 53s-5.04 12.69-11.43 12.69Z" />
</svg>
);
};
});
@@ -8,7 +8,7 @@ import NextLink from "next/link";
import { ROUTES } from "src/lib/routes";
import { Message } from "src/types/Conversation";
import { isKnownEmoji } from "src/types/Emoji";
import { StrictOmit } from "src/types/utils";
import { StrictOmit } from "ts-essentials";
import { DataTable, DataTableProps } from "../DataTable/DataTable";
import { DataTableAction } from "../DataTable/DataTableAction";
@@ -1,5 +1,6 @@
import {
Avatar,
AvatarProps,
Box,
HStack,
Menu,
@@ -15,7 +16,18 @@ import {
useToast,
} from "@chakra-ui/react";
import { boolean } from "boolean";
import { ClipboardList, Copy, Flag, Link, MessageSquare, MoreHorizontal, Slash, Trash, User } from "lucide-react";
import {
ClipboardList,
Copy,
Flag,
Link,
MessageSquare,
MoreHorizontal,
Shield,
Slash,
Trash,
User,
} from "lucide-react";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";
import { useCallback, useEffect, useMemo, useState } from "react";
@@ -24,6 +36,7 @@ import { MessageEmojiButton } from "src/components/Messages/MessageEmojiButton";
import { ReportPopup } from "src/components/Messages/ReportPopup";
import { useHasAnyRole } from "src/hooks/auth/useHasAnyRole";
import { del, post, put } from "src/lib/api";
import { ROUTES } from "src/lib/routes";
import { colors } from "src/styles/Theme/colors";
import { Message, MessageEmojis } from "src/types/Conversation";
import { emojiIcons, isKnownEmoji } from "src/types/Emoji";
@@ -34,9 +47,17 @@ interface MessageTableEntryProps {
message: Message;
enabled?: boolean;
highlight?: boolean;
avartarPosition?: "middle" | "top";
avartarProps?: AvatarProps;
}
export function MessageTableEntry({ message, enabled, highlight }: MessageTableEntryProps) {
export function MessageTableEntry({
message,
enabled,
highlight,
avartarPosition = "middle",
avartarProps,
}: MessageTableEntryProps) {
const router = useRouter();
const [emojiState, setEmojis] = useState<MessageEmojis>({ emojis: {}, user_emojis: [] });
useEffect(() => {
@@ -68,9 +89,10 @@ export function MessageTableEntry({ message, enabled, highlight }: MessageTableE
mr={inlineAvatar ? 2 : 0}
name={`${boolean(message.is_assistant) ? "Assistant" : "User"}`}
src={`${boolean(message.is_assistant) ? "/images/logos/logo.png" : "/images/temp-avatars/av1.jpg"}`}
{...avartarProps}
/>
),
[borderColor, inlineAvatar, message.is_assistant]
[avartarProps, borderColor, inlineAvatar, message.is_assistant]
);
const highlightColor = useColorModeValue(colors.light.active, colors.dark.active);
@@ -86,13 +108,17 @@ export function MessageTableEntry({ message, enabled, highlight }: MessageTableE
};
return (
<HStack w={["full", "full", "full", "fit-content"]} gap={2}>
<HStack
w={["full", "full", "full", "fit-content"]}
gap={0.5}
alignItems={avartarPosition === "top" ? "start" : "center"}
>
{!inlineAvatar && avatar}
<Box
width={["full", "full", "full", "fit-content"]}
maxWidth={["full", "full", "full", "2xl"]}
p="4"
borderRadius="md"
borderRadius="18px"
bg={message.is_assistant ? backgroundColor : backgroundColor2}
outline={highlight && "2px solid black"}
outlineColor={highlightColor}
@@ -249,6 +275,9 @@ const MessageActions = ({
<MenuItem onClick={() => handleCopy(id)} icon={<Copy />}>
{t("copy_message_id")}
</MenuItem>
<MenuItem as="a" href={ROUTES.ADMIN_MESSAGE_DETAIL(message.id)} target="_blank" icon={<Shield />}>
View in admin area
</MenuItem>
<MenuItem as="a" href={`/admin/manage_user/${message.user_id}`} target="_blank" icon={<User />}>
{t("view_user")}
</MenuItem>
@@ -0,0 +1,104 @@
import { Box } from "@chakra-ui/react";
import { Fragment } from "react";
import { MessageWithChildren } from "src/types/Conversation";
import { MessageTableEntry } from "./MessageTableEntry";
const connectionColor = "gray.300";
const messagePaddingTop = 16;
const avatarSize = 32;
const avartarMarginTop = 6;
const maxDepth = 100; // this only used for debug UI in mobile
const left = avatarSize / 2 - 1;
export const MessageTree = ({ tree, messageId }: { tree: MessageWithChildren; messageId?: string }) => {
const renderChildren = (children: MessageWithChildren[], depth = 1) => {
const hasSibling = children.length > 1;
return children.map((child, idx) => {
const hasChildren = child.children.length > 0;
const isLastChild = idx === children.length - 1;
return (
<Fragment key={child.id}>
<Box position="relative" className="box2">
<ConnectionCurve></ConnectionCurve>
<Box paddingLeft={`32px`} position="relative" className="box3">
{hasSibling && !isLastChild && (
<Box
height={`calc(100% - 26px)`}
position="absolute"
width="2px"
bg="gray.300"
left={`${left}px`}
top="26px"
></Box>
)}
<Box pt={`${messagePaddingTop}px`} position="relative" className="box4">
{hasChildren && depth < maxDepth && <Connection className="connection1"></Connection>}
<MessageTableEntry
avartarProps={{
mt: `${avartarMarginTop}px`,
}}
avartarPosition="top"
highlight={child.id === messageId}
message={child}
></MessageTableEntry>
</Box>
{depth < maxDepth && renderChildren(child.children, depth + 1)}
</Box>
</Box>
</Fragment>
);
});
};
return (
<>
<Box position="relative">
<Box height="full" position="absolute" width="2px" bg={connectionColor} left={`${left}px`}></Box>
<MessageTableEntry
message={tree}
avartarPosition="top"
highlight={tree.id === messageId}
avartarProps={{
size: "sm",
}}
></MessageTableEntry>
</Box>
{renderChildren(tree.children)}
</>
);
};
const Connection = ({ className, isSibling = false }: { isSibling?: boolean; className?: string }) => {
const top = isSibling ? `26px` : `32px`;
return (
<Box
height={`calc(100% - ${top})`}
position="absolute"
width="2px"
bg="gray.300"
left={`${left}px`}
top={top}
className={className}
></Box>
);
};
const height = avatarSize / 2 + avartarMarginTop + messagePaddingTop;
const width = avatarSize / 2 + 10;
const ConnectionCurve = () => {
return (
<Box
position="absolute"
height={`${height}px`}
width={`${width}px`}
left={`${left}px `}
borderBottomWidth="2px"
borderBottomLeftRadius="10px"
borderLeftStyle="solid"
borderLeftWidth="2px"
borderColor={connectionColor}
className="curve"
></Box>
);
};
+10 -4
View File
@@ -1,9 +1,15 @@
import { Select, SelectProps } from "@chakra-ui/react";
import { forwardRef } from "react";
import { ElementOf } from "src/types/utils";
import { ValueOf } from "ts-essentials";
export const roles = ["general", "admin", "banned", "moderator"] as const;
export type Role = ElementOf<typeof roles>;
export const ROLES = {
GERNERAL: "general",
BANNED: "banned",
ADMIN: "admin",
MODERATOR: "moderator",
} as const;
export type Role = ValueOf<typeof ROLES>;
type RoleSelectProps = Omit<SelectProps, "defaultValue"> & {
defaultValue?: Role;
@@ -13,7 +19,7 @@ type RoleSelectProps = Omit<SelectProps, "defaultValue"> & {
export const RoleSelect = forwardRef<HTMLSelectElement, RoleSelectProps>((props, ref) => {
return (
<Select {...props} ref={ref}>
{roles.map((role) => (
{Object.values(ROLES).map((role) => (
<option value={role} key={role}>
{role}
</option>
@@ -12,23 +12,33 @@ const Template = ({ items, isEditable, isDisabled }) => {
return <Sortable items={items} isEditable={isEditable} isDisabled={isDisabled} className="my-8" />;
};
export const Default = Template.bind({});
Default.args = {
const props = {
items: [
"Who were the 8 presidents before George Washington?",
"euirdteunvglfe23908230892309832098 AAAAAAAA",
"Sorry, my cat sat on my keyboard. Can you print a cat in ASCII art?",
"This is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\n",
],
isEditable: true,
isDisabled: false,
};
export const Default = Template.bind({});
Default.args = props;
export const NotEditable = Template.bind({});
NotEditable.args = {
...props,
isEditable: false,
};
export const LongText = Template.bind({});
LongText.args = {
items: [
"Okay, here\u2019s my answer. The thing is, in a job interview you are applying to the position, right? Which means that you need to try to make yourself sound as good a fit as possible. So that means it\u2019s a mistake to wear something that people would think of as a silly or stupid outfit. It\u2019s good to wear something in line with what people expect the job to require, and ideally you want to wear something that you can walk around comfortably in. At the same time, if you wear something that looks kind of dorky, you might not stand out as much as someone who doesn\u2019t, because the other candidates probably all have something slightly weird or wacky about them too. So, I\u2019d say, do you like your outfit? Wear something that you\u2019re comfortable in, that you think you look good in, and is in line with what the job would require. Also, for interview prep, I would focus on making yourself look as professional as possible, from your hair to your outfit. What do you think of this?",
"Assistant: Yes, I think they can be helpful when the child misbehaves, but they should be used with a little bit of compassion and understanding that it\u2019s not the natural state of things to have an adult yelling at them. Time outs are also often used without letting the child know how they\u2019re getting out of the time out, which can make it feel arbitrary or like a punishment, rather than a consequence for something they did. It\u2019s really easy for adults to do this kind of thing unconsciously. It\u2019s easy to get caught up in the notion that \u201cThey\u2019re in time out, and that\u2019s the end of it!\u201d but kids can be pretty imaginative, and they can use their own creativity to make their way out of time outs. A compassionate time out ends when the child shows a sign of understanding what they\u2019ve done wrong, and are ready to begin again. That way the child knows they\u2019re learning, and that the parent is seeing them as an intelligent person, even if they sometimes mess up. You can still use the other techniques you were using to be tough when necessary, but using a compassionate approach will let you use them without actually using them!",
"Assistant: No. The USA was founded by a Puritan group of Protestants, but it didn\u2019t adopt the religion of the Puritans until much later, and it was always a secular state. The Puritans observed the Sabbath on Sunday, and the Puritans only had a small influence in the early history of the USA. It\u2019s difficult to trace the origins of closing stores on Sunday, but one early and short-lived attempt at forcing the Sabbath on people in the 1800s was motivated by the Protestant ideal that people should spend Sunday focusing on spiritual activities. By the mid-1800s, when the Sunday closing law was made, there was not a lot of pressure from that standpoint, but the church had begun to advocate for Sunday closing laws as a way of counteracting the negative effects of industrialization on the day of rest. Even after that shift, closing stores on Sunday was not always possible, since the religious Sunday was not always chosen for observance. And as industrialization accelerated and mechanization made it possible to operate stores on Sunday, the law was not enforced as much as people liked. The day of rest was also being violated by stores that stayed open all day on Sunday, so closing stores on Sundays became an effort to protect the Sabbath for all citizens.",
"This is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\nThis is a new line\n",
],
isEditable: true,
isDisabled: false,
@@ -2,7 +2,7 @@ import { Box, useColorModeValue } from "@chakra-ui/react";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { GripVertical } from "lucide-react";
import { PropsWithChildren, useState } from "react";
import { PropsWithChildren, useMemo } from "react";
export const SortableItem = ({
children,
@@ -13,37 +13,39 @@ export const SortableItem = ({
}: PropsWithChildren<{ id: number; index: number; isEditable: boolean; isDisabled: boolean }>) => {
const backgroundColor = useColorModeValue("gray.700", "gray.500");
const disabledBackgroundColor = useColorModeValue("gray.400", "gray.700");
const activeBackgroundColor = useColorModeValue("gray.600", "gray.600");
const textColor = useColorModeValue("white", "white");
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id, disabled: !isEditable });
const style = {
transform: CSS.Translate.toString(transform),
transition,
touchAction: "none",
};
const [grabbing, setGrabbing] = useState(false);
const sx = useMemo(
() => ({
"&:active": {
bg: `${isEditable ? activeBackgroundColor : backgroundColor}`,
cursor: `${isEditable ? "grabbing" : "default"}`,
},
touchAction: "none",
}),
[isEditable, activeBackgroundColor, backgroundColor]
);
return (
<Box
sx={sx}
transform={CSS.Translate.toString(transform)}
transition={transition}
display="flex"
alignItems="center"
bg={isDisabled ? disabledBackgroundColor : backgroundColor}
borderRadius="lg"
p="4"
whiteSpace="pre-wrap"
color={textColor}
cursor={isEditable ? (grabbing ? "grabbing" : "grab") : "auto"}
aria-roledescription="sortable"
onMouseDown={() => {
setGrabbing(true);
}}
onMouseUp={() => setGrabbing(false)}
ref={setNodeRef}
shadow="base"
{...attributes}
{...listeners}
ref={setNodeRef}
style={style}
shadow="base"
>
<Box pr="4">{isEditable ? <GripVertical size="20px" /> : `${index + 1}.`}</Box>
{children}
@@ -1,4 +1,5 @@
import { Box, Grid, GridItem, Text, useColorModeValue } from "@chakra-ui/react";
import { useTranslation } from "next-i18next";
import React from "react";
import { useState } from "react";
import { LikertButtons } from "src/components/Buttons/LikertButtons";
@@ -18,160 +19,154 @@ interface LabelInfo {
inverted: boolean;
}
const getLabelInfo = (label: string): LabelInfo => {
const getLabelInfo = (label: string, t: (key: string) => string): LabelInfo => {
switch (label) {
case "spam":
return {
zeroText: "Not Spam",
zeroDescription: ["Suitable for training Open Assistant."],
oneText: "Spam",
zeroText: t("not_spam"),
zeroDescription: [t("not_spam.explanation")],
oneText: t("spam"),
oneDescription: [
"Seems to be intentionally low-quality or irrelevant",
'We consider the following unwanted content as spam: trolling, intentional undermining of our purpose, illegal material, material that violates our code of conduct, and other things that are inappropriate for our dataset. We collect these under the common heading of "spam".',
"This is not an assessment of whether this message is the best possible answer. Especially for prompts or user-replies, we very much want to retain all kinds of responses in the dataset, so that the assistant can learn to reply appropriately.",
"Please mark this text as spam only if it is clearly unsuited to be part of our dataset, as outlined above, and try not to make any subjective value-judgments beyond that.",
t("spam.one_desc.line_1"),
t("spam.one_desc.line_2"),
t("spam.one_desc.line_3"),
t("spam.one_desc.line_4"),
],
inverted: true,
};
case "fails_task":
return {
zeroText: "Follows Instructions",
zeroText: t("follows_instructions"),
zeroDescription: [],
oneText: "Fails Task",
oneDescription: ["Fails to follow the correct instruction / task"],
oneText: t("fails_task"),
oneDescription: [t("fails_task.one_desc")],
inverted: true,
};
case "not_appropriate":
return {
zeroText: "Appropriate",
zeroText: t("appropriate"),
zeroDescription: [],
oneText: "Inappropriate",
oneDescription: ["Inappropriate for customer assistant"],
oneText: t("inappropriate"),
oneDescription: [t("inappropriate.one_desc")],
inverted: true,
};
case "violence":
return {
zeroText: "Harmless",
zeroText: t("harmless"),
zeroDescription: [],
oneText: "Violent",
oneDescription: ["Encourages or fails to discourage violence/abuse/terrorism/self-harm"],
oneText: t("violent"),
oneDescription: [t("violent.one_desc")],
inverted: true,
};
case "excessive_harm":
return {
zeroText: "Safe",
zeroText: t("safe"),
zeroDescription: [],
oneText: "Harmful",
oneDescription: [
"Content likely to cause excessive harm not justifiable in the context",
"Harm refers to physical or mental damage or injury to someone or something. Excessive refers to a reasonable threshold of harm in the context, for instance damaging skin is not excessive in the context of surgery.",
],
oneText: t("harmful"),
oneDescription: [t("harmful.one_desc.line_1"), t("harmful.one_desc.line_2")],
inverted: true,
};
case "sexual_content":
return {
zeroText: "Non Sexual",
zeroText: t("non_sexual"),
zeroDescription: [],
oneText: "Sexual",
oneDescription: ["Contains sexual content"],
oneText: t("sexual"),
oneDescription: [t("sexual.one_desc")],
inverted: true,
};
case "toxicity":
return {
zeroText: "Polite",
zeroText: t("polite"),
zeroDescription: [],
oneText: "Rude",
oneDescription: ["Contains rude, abusive, profane or insulting content"],
oneText: t("rude"),
oneDescription: [t("rude.one_desc")],
inverted: true,
};
case "moral_judgement":
return {
zeroText: "Non-Judgemental",
zeroText: t("non_judgemental"),
zeroDescription: [],
oneText: "Judgemental",
oneDescription: ["Expresses moral judgement"],
oneText: t("judgemental"),
oneDescription: [t("judgemental.one_desc")],
inverted: true,
};
case "political_content":
return {
zeroText: "Apolitical",
zeroText: t("apolitical"),
zeroDescription: [],
oneText: "Political",
oneDescription: ["Expresses political views"],
oneText: t("political"),
oneDescription: [t("political.one_desc")],
inverted: true,
};
case "humor":
return {
zeroText: "Serious",
zeroText: t("serious"),
zeroDescription: [],
oneText: "Humorous",
oneDescription: ["Contains humorous content including sarcasm"],
oneText: t("humorous"),
oneDescription: [t("humorous.one_desc")],
inverted: false,
};
case "hate_speech":
return {
zeroText: "Safe",
zeroText: t("safe"),
zeroDescription: [],
oneText: "Hateful",
oneDescription: [
"Content is abusive or threatening and expresses prejudice against a protected characteristic",
"Prejudice refers to preconceived views not based on reason. Protected characteristics include gender, ethnicity, religion, sexual orientation, and similar characteristics.",
],
oneText: t("hateful"),
oneDescription: [t("hateful.one_desc.line_1"), t("hateful.one_desc.line_2")],
inverted: true,
};
case "threat":
return {
zeroText: "Safe",
zeroText: t("safe"),
zeroDescription: [],
oneText: "Threatening",
oneDescription: ["Contains a threat against a person or persons"],
oneText: t("threatening"),
oneDescription: [t("threatening.one_desc")],
inverted: true,
};
case "misleading":
return {
zeroText: "Accurate",
zeroText: t("accurate"),
zeroDescription: [],
oneText: "Misleading",
oneDescription: ["Contains text which is incorrect or misleading"],
oneText: t("misleading"),
oneDescription: [t("misleading.one_desc")],
inverted: true,
};
case "helpfulness":
return {
zeroText: "Unhelpful",
zeroText: t("unhelpful"),
zeroDescription: [],
oneText: "Helpful",
oneDescription: ["Completes the task to a high standard"],
oneText: t("helpful"),
oneDescription: [t("helpful.one_desc")],
inverted: false,
};
case "creative":
return {
zeroText: "Boring",
zeroText: t("boring"),
zeroDescription: [],
oneText: "Creative",
oneDescription: ["Expresses creativity in responding to the task"],
oneText: t("creative"),
oneDescription: [t("creative.one_desc")],
inverted: false,
};
case "pii":
return {
zeroText: "Clean",
zeroText: t("clean"),
zeroDescription: [],
oneText: "Contains PII",
oneDescription: ["Contains personally identifying information"],
oneText: t("contains_pii"),
oneDescription: [t("contains_pii.one_desc")],
inverted: false,
};
case "quality":
return {
zeroText: "Low Quality",
zeroText: t("low_quality"),
zeroDescription: [],
oneText: "High Quality",
oneText: t("high_quality"),
oneDescription: [],
inverted: false,
};
case "creativity":
return {
zeroText: "Ordinary",
zeroText: t("ordinary"),
zeroDescription: [],
oneText: "Creative",
oneText: t("creative"),
oneDescription: [],
inverted: false,
};
@@ -187,6 +182,7 @@ const getLabelInfo = (label: string): LabelInfo => {
};
export const LabelLikertGroup = ({ labelIDs, onChange, isEditable = true }: LabelInputGroupProps) => {
const { t } = useTranslation("labelling");
const [labelValues, setLabelValues] = useState<number[]>(Array.from({ length: labelIDs.length }).map(() => null));
const cardColor = useColorModeValue("gray.50", "gray.800");
@@ -194,7 +190,7 @@ export const LabelLikertGroup = ({ labelIDs, onChange, isEditable = true }: Labe
return (
<Grid templateColumns={"minmax(min-content, 30em)"} rowGap={2}>
{labelIDs.map((labelId, idx) => {
const { zeroText, oneText, zeroDescription, oneDescription, inverted } = getLabelInfo(labelId);
const { zeroText, oneText, zeroDescription, oneDescription, inverted } = getLabelInfo(labelId, t);
let textA = zeroText;
let textB = oneText;
@@ -1,4 +1,5 @@
import { Box, Flex, IconButton, Progress, Tooltip, useColorModeValue } from "@chakra-ui/react";
import { useTranslation } from "next-i18next";
import { Edit2 } from "lucide-react";
import { SkipButton } from "src/components/Buttons/Skip";
import { SubmitButton } from "src/components/Buttons/Submit";
@@ -25,13 +26,14 @@ export const TaskControls = ({
onSubmit,
onSkip,
}: TaskControlsProps) => {
const { t } = useTranslation();
const backgroundColor = useColorModeValue("white", "gray.800");
return (
<Box width="full" bg={backgroundColor} borderRadius="xl" shadow="base">
{isLoading && <Progress size="sm" isIndeterminate />}
<Flex p="6" gap="4" direction={["column", "row"]}>
<TaskInfo id={task.id} output="Submit your answer" />
<TaskInfo id={task.id} output={t("submit_your_answer")} />
<Flex width={["full", "fit-content"]} justify="center" ml="auto" gap={2}>
{taskStatus.mode === "EDIT" ? (
<>
@@ -42,7 +44,7 @@ export const TaskControls = ({
isDisabled={taskStatus.replyValidity === "INVALID"}
onClick={onReview}
>
Review
{t("review")}
</SubmitButton>
</>
) : (
@@ -56,7 +58,7 @@ export const TaskControls = ({
isDisabled={taskStatus.mode === "SUBMITTED"}
onClick={onSubmit}
>
Submit
{t("submit")}
</SubmitButton>
</>
)}
+4 -2
View File
@@ -1,4 +1,5 @@
import { Box, Flex, Text, TextProps, useColorModeValue } from "@chakra-ui/react";
import { useTranslation } from "next-i18next";
const TitleClasses: TextProps = {
fontWeight: "semibold",
@@ -13,6 +14,7 @@ const LabelClasses: TextProps = {
};
export const TaskInfo = ({ id, output }: { id: string; output: string }) => {
const { t } = useTranslation();
const titleColor = useColorModeValue("gray.700", "gray.400");
return (
@@ -20,7 +22,7 @@ export const TaskInfo = ({ id, output }: { id: string; output: string }) => {
<Flex direction="column">
<Flex alignItems="center" gap="2">
<Text {...TitleClasses} color={titleColor}>
Prompt
{t("prompt")}
</Text>
<Text {...LabelClasses} data-cy="task-id">
{id}
@@ -28,7 +30,7 @@ export const TaskInfo = ({ id, output }: { id: string; output: string }) => {
</Flex>
<Flex alignItems="center" gap="2">
<Text {...TitleClasses} color={titleColor}>
Output
{t("output")}
</Text>
<Text {...LabelClasses}>{output}</Text>
</Flex>
+9 -5
View File
@@ -1,11 +1,15 @@
const missingDisplayNamesForLocales = {
eu: "Euskara",
gl: "Galego",
};
/**
* Returns the locale's name.
*/
export const getLocaleDisplayName = (locale, displayLocale = undefined) => {
// Different browsers seem to handle "eu" differently from the Node server.
// Special case this to avoid a hydration failure.
if (locale === "eu") {
return "Euskara";
export const getLocaleDisplayName = (locale: string, displayLocale = undefined) => {
// Intl defaults to English for locales that are not oficially translated
if (missingDisplayNamesForLocales[locale]) {
return missingDisplayNamesForLocales[locale];
}
const displayName = new Intl.DisplayNames([displayLocale || locale], { type: "language" }).of(locale);
// Return the Titlecased version of the language name.
+7
View File
@@ -189,6 +189,13 @@ export class OasstApiClient {
return this.get<Message>(`/api/v1/messages/${message_id}?username=${user.id}&auth_method=${user.auth_method}`);
}
async fetch_message_tree(message_id: string) {
return this.get<{
id: string;
messages: Message[];
}>(`/api/v1/messages/${message_id}/tree`);
}
/**
* Delete a message by its id
*/
+4 -4
View File
@@ -5,7 +5,7 @@ import { useSession } from "next-auth/react";
import React from "react";
export { getDefaultStaticProps as getStaticProps } from "src/lib/default_static_props";
import { Pencil } from "lucide-react";
import { useTranslation } from "react-i18next";
import { useTranslation } from "next-i18next";
import { SurveyCard } from "src/components/Survey/SurveyCard";
import { get } from "src/lib/api";
import { getTypeSafei18nKey } from "src/lib/i18n";
@@ -41,15 +41,15 @@ export default function Account() {
<Title>{t("your_account")}</Title>
<Divider />
<Grid gridTemplateColumns="repeat(2, max-content)" alignItems="center" gap={6} py={4}>
<Text as="b">Username</Text>
<Text as="b">{t("username")}</Text>
<Flex gap={2}>
{session.user.name ?? "(No username)"}
{session.user.name ?? t("no_username")}
<Link href="/account/edit">
<Icon boxSize={5} as={Pencil} size="1em" />
</Link>
</Flex>
<Text as="b">Email</Text>
<Text>{session.user.email ?? "(No Email)"}</Text>
<Text>{session.user.email ?? t("no_email")}</Text>
</Grid>
</SurveyCard>
<SurveyCard>
+68
View File
@@ -0,0 +1,68 @@
import { Card, CardBody, CardHeader, CircularProgress, Grid } from "@chakra-ui/react";
import { GetServerSideProps } from "next";
import Head from "next/head";
import { useRouter } from "next/router";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { AdminArea } from "src/components/AdminArea";
import { JsonCard } from "src/components/JsonCard";
import { getAdminLayout } from "src/components/Layout";
import { MessageTree } from "src/components/Messages/MessageTree";
import { get } from "src/lib/api";
import { Message, MessageWithChildren } from "src/types/Conversation";
import useSWRImmutable from "swr/immutable";
const MessageDetail = () => {
const router = useRouter();
const messageId = router.query.id;
const { data, isLoading, error } = useSWRImmutable<{
tree: MessageWithChildren | null;
message?: Message;
}>(`/api/admin/messages/${messageId}/tree`, get);
return (
<>
<Head>
<title>Open Assistant</title>
</Head>
<AdminArea>
{isLoading && <CircularProgress isIndeterminate></CircularProgress>}
{error && "Unable to load message tree"}
{data &&
(data.tree === null ? (
"Unable to build tree"
) : (
<Grid gap="6">
<Card>
<CardHeader fontWeight="bold" fontSize="xl" pb="0">
Message Detail
</CardHeader>
<CardBody>
<JsonCard>{data.message}</JsonCard>
</CardBody>
</Card>
<Card>
<CardHeader fontWeight="bold" fontSize="xl" pb="0">
Tree {data.tree.id}
</CardHeader>
<CardBody>
<MessageTree tree={data.tree} messageId={data.message?.id}></MessageTree>
</CardBody>
</Card>
</Grid>
))}
</AdminArea>
</>
);
};
MessageDetail.getLayout = getAdminLayout;
export default MessageDetail;
export const getServerSideProps: GetServerSideProps = async ({ locale = "en" }) => {
return {
props: {
...(await serverSideTranslations(locale, ["common", "labelling", "message"])),
},
};
};
@@ -0,0 +1,52 @@
import { withAnyRole } from "src/lib/auth";
import { createApiClient } from "src/lib/oasst_client_factory";
import { Message, MessageWithChildren } from "src/types/Conversation";
export default withAnyRole(["admin", "moderator"], async (req, res, token) => {
const client = await createApiClient(token);
const messageId = req.query.id as string;
const response = await client.fetch_message_tree(messageId);
if (!response) {
return res.json({ tree: null });
}
const tree = buildTree(response.messages);
return res.json({ tree, message: response.messages.find((m) => m.id === messageId) });
});
// https://medium.com/@lizhuohang.selina/building-a-hierarchical-tree-from-a-flat-list-an-easy-to-understand-solution-visualisation-19cb24bdfa33
const buildTree = (messages: Message[]): MessageWithChildren | null => {
const map: Record<string, MessageWithChildren> = {};
const tree = [];
// Build a hash table and map items to objects
messages.forEach(function (item) {
const id = item.id;
if (!map[id]) {
map[id] = { ...item, children: [] };
}
});
// Loop over hash table
let mappedElem: MessageWithChildren;
for (const id in map) {
if (map[id]) {
mappedElem = map[id];
// If the element is not at the root level, add it to its parent array of children. Note this will continue till we have only root level elements left
if (mappedElem.parent_id) {
const parentId = mappedElem.parent_id;
map[parentId].children.push(mappedElem);
}
// If the element is at the root level, directly push to the tree
else {
tree.push(mappedElem);
}
}
}
return tree.shift() || null;
};
+13 -11
View File
@@ -1,21 +1,23 @@
import { withRole } from "src/lib/auth";
import { ROLES } from "src/components/RoleSelect";
import { withAnyRole } from "src/lib/auth";
import { createApiClient } from "src/lib/oasst_client_factory";
import prisma from "src/lib/prismadb";
/**
* Update's the user's data in the database. Accessible only to admins.
*/
const handler = withRole("admin", async (req, res, token) => {
const { id, auth_method, user_id, notes, role, show_on_leaderboard } = req.body;
const oasstApiClient = await createApiClient(token);
// If the user is authorized by the web, update their role.
if (auth_method === "local") {
await prisma.user.update({
where: { id },
data: { role },
});
const handler = withAnyRole(["admin", "moderator"], async (req, res, token) => {
const { id, user_id, notes, role, show_on_leaderboard } = req.body;
// mod can't update user role to mod or admin
if (token.role === ROLES.MODERATOR && (role === ROLES.MODERATOR || role === ROLES.ADMIN)) {
return res.status(403).json({});
}
const oasstApiClient = await createApiClient(token);
await prisma.user.update({
where: { id },
data: { role },
});
// Tell the backend the user's enabled or not enabled status.
await oasstApiClient.set_user_status(user_id, role !== "banned", notes, show_on_leaderboard);
+5 -1
View File
@@ -16,7 +16,7 @@ export interface Message extends MessageEmojis {
is_assistant: boolean;
lang: string;
created_date: string; // iso date string
parent_id: string;
parent_id: string | null;
frontend_message_id?: string;
user_id: string;
user_is_author: boolean | null;
@@ -40,3 +40,7 @@ export type FetchUserMessagesCursorResponse = {
items: Message[];
order: "asc" | "desc";
};
export type MessageWithChildren = Message & {
children: MessageWithChildren[];
};
-10
View File
@@ -1,10 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
// https://github.com/ts-essentials/ts-essentials/blob/25cae45c162f8784e3cdae8f43783d0c66370a57/lib/types.ts#L437
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ElementOf<T extends readonly any[]> = T extends readonly (infer ET)[] ? ET : never;
type AnyRecord<T = any> = Record<KeyofBase, T>;
type KeyofBase = keyof any;
export type AnyArray<T = any> = Array<T> | ReadonlyArray<T>;
export type StrictOmit<T extends AnyRecord, K extends keyof T> = T extends AnyArray ? never : Omit<T, K>;