mirror of
https://github.com/wassname/Open-Assistant.git
synced 2026-06-27 16:10:30 +08:00
Merge branch 'main' of github.com:LAION-AI/Open-Chat-GPT
This commit is contained in:
@@ -32,7 +32,7 @@ jobs:
|
||||
WEB_EMAIL_SERVER_PASSWORD: ${{ secrets.DEV_WEB_EMAIL_SERVER_PASSWORD }}
|
||||
WEB_EMAIL_SERVER_PORT: ${{ secrets.DEV_WEB_EMAIL_SERVER_PORT }}
|
||||
WEB_EMAIL_SERVER_USER: ${{ secrets.DEV_WEB_EMAIL_SERVER_USER }}
|
||||
WEB_NEXTAUTH_SECRET: ${{ secrets.DEV_WEB_NEXTAUTH_SECRET }}
|
||||
WEB_NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
|
||||
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
|
||||
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
|
||||
AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_KEY }}
|
||||
|
||||
@@ -13,6 +13,7 @@ from oasst_backend.models import Message, MessageReaction, MessageTreeState, Tas
|
||||
from oasst_backend.prompt_repository import PromptRepository
|
||||
from oasst_backend.utils.database_utils import CommitMode, async_managed_tx_method, managed_tx_method
|
||||
from oasst_backend.utils.hugging_face import HfClassificationModel, HfEmbeddingModel, HfUrl, HuggingFaceAPI
|
||||
from oasst_backend.utils.ranking import ranked_pairs
|
||||
from oasst_shared.exceptions.oasst_api_error import OasstError, OasstErrorCode
|
||||
from oasst_shared.schemas import protocol as protocol_schema
|
||||
from sqlalchemy.sql import text
|
||||
@@ -130,7 +131,7 @@ class TreeManager:
|
||||
def _determine_task_availability_internal(
|
||||
self,
|
||||
num_active_trees: int,
|
||||
extensible_parents: list[ExtendibleParentRow],
|
||||
extendible_parents: list[ExtendibleParentRow],
|
||||
prompts_need_review: list[Message],
|
||||
replies_need_review: list[Message],
|
||||
incomplete_rankings: list[IncompleteRankingsRow],
|
||||
@@ -141,17 +142,17 @@ class TreeManager:
|
||||
task_count_by_type[protocol_schema.TaskRequestType.initial_prompt] = num_missing_prompts
|
||||
|
||||
task_count_by_type[protocol_schema.TaskRequestType.prompter_reply] = len(
|
||||
list(filter(lambda x: x.parent_role == "assistant", extensible_parents))
|
||||
list(filter(lambda x: x.parent_role == "assistant", extendible_parents))
|
||||
)
|
||||
task_count_by_type[protocol_schema.TaskRequestType.assistant_reply] = len(
|
||||
list(filter(lambda x: x.parent_role == "prompter", extensible_parents))
|
||||
list(filter(lambda x: x.parent_role == "prompter", extendible_parents))
|
||||
)
|
||||
|
||||
task_count_by_type[protocol_schema.TaskRequestType.label_initial_prompt] = len(prompts_need_review)
|
||||
task_count_by_type[protocol_schema.TaskRequestType.label_assistant_reply] = len(
|
||||
list(filter(lambda m: m.role == "assistant", replies_need_review))
|
||||
)
|
||||
task_count_by_type[protocol_schema.TaskRequestType.prompter_reply] = len(
|
||||
task_count_by_type[protocol_schema.TaskRequestType.label_prompter_reply] = len(
|
||||
list(filter(lambda m: m.role == "prompter", replies_need_review))
|
||||
)
|
||||
|
||||
@@ -172,14 +173,14 @@ class TreeManager:
|
||||
|
||||
def determine_task_availability(self) -> dict[protocol_schema.TaskRequestType, int]:
|
||||
num_active_trees = self.query_num_active_trees()
|
||||
extensible_parents = self.query_extendible_parents()
|
||||
extendible_parents = self.query_extendible_parents()
|
||||
prompts_need_review = self.query_prompts_need_review()
|
||||
replies_need_review = self.query_replies_need_review()
|
||||
incomplete_rankings = self.query_incomplete_rankings()
|
||||
|
||||
return self._determine_task_availability_internal(
|
||||
num_active_trees=num_active_trees,
|
||||
extensible_parents=extensible_parents,
|
||||
extendible_parents=extendible_parents,
|
||||
prompts_need_review=prompts_need_review,
|
||||
replies_need_review=replies_need_review,
|
||||
incomplete_rankings=incomplete_rankings,
|
||||
@@ -194,7 +195,7 @@ class TreeManager:
|
||||
num_active_trees = self.query_num_active_trees()
|
||||
prompts_need_review = self.query_prompts_need_review()
|
||||
replies_need_review = self.query_replies_need_review()
|
||||
extensible_parents = self.query_extendible_parents()
|
||||
extendible_parents = self.query_extendible_parents()
|
||||
|
||||
incomplete_rankings = self.query_incomplete_rankings()
|
||||
if not self.cfg.rank_prompter_replies:
|
||||
@@ -224,7 +225,7 @@ class TreeManager:
|
||||
else:
|
||||
task_count_by_type = self._determine_task_availability_internal(
|
||||
num_active_trees=num_active_trees,
|
||||
extensible_parents=extensible_parents,
|
||||
extendible_parents=extendible_parents,
|
||||
prompts_need_review=prompts_need_review,
|
||||
replies_need_review=replies_need_review,
|
||||
incomplete_rankings=incomplete_rankings,
|
||||
@@ -356,12 +357,12 @@ class TreeManager:
|
||||
case TaskType.REPLY:
|
||||
# select a tree with missing replies
|
||||
if task_role == TaskRole.PROMPTER:
|
||||
extensible_parents = list(filter(lambda x: x.parent_role == "assistant", extensible_parents))
|
||||
extendible_parents = list(filter(lambda x: x.parent_role == "assistant", extendible_parents))
|
||||
elif task_role == TaskRole.ASSISTANT:
|
||||
extensible_parents = list(filter(lambda x: x.parent_role == "prompter", extensible_parents))
|
||||
extendible_parents = list(filter(lambda x: x.parent_role == "prompter", extendible_parents))
|
||||
|
||||
if len(extensible_parents) > 0:
|
||||
random_parent = random.choice(extensible_parents)
|
||||
if len(extendible_parents) > 0:
|
||||
random_parent = random.choice(extendible_parents)
|
||||
|
||||
# fetch random conversation to extend
|
||||
logger.debug(f"selected {random_parent=}")
|
||||
@@ -587,6 +588,7 @@ class TreeManager:
|
||||
self._enter_state(mts, message_tree_state.State.RANKING)
|
||||
return True
|
||||
|
||||
@managed_tx_method(CommitMode.COMMIT)
|
||||
def check_condition_for_scoring_state(self, message_tree_id: UUID) -> bool:
|
||||
logger.debug(f"check_condition_for_scoring_state({message_tree_id=})")
|
||||
mts: MessageTreeState
|
||||
@@ -603,8 +605,24 @@ class TreeManager:
|
||||
return False
|
||||
|
||||
self._enter_state(mts, message_tree_state.State.READY_FOR_SCORING)
|
||||
self.update_message_ranks(rankings_by_message)
|
||||
return True
|
||||
|
||||
@managed_tx_method(CommitMode.COMMIT)
|
||||
def update_message_ranks(self, rankings_by_message: Dict[int, int]) -> None:
|
||||
for parent_msg_id, ranking in rankings_by_message.items():
|
||||
sorted_messages = []
|
||||
for msg_reaction in ranking:
|
||||
sorted_messages.append(msg_reaction.payload.payload.ranked_message_ids)
|
||||
logger.debug(f"SORTED MESSAGE {sorted_messages}")
|
||||
consensus = ranked_pairs(sorted_messages)
|
||||
logger.debug(f"CONSENSUS: {consensus}\n\n")
|
||||
for rank, message_id in enumerate(consensus):
|
||||
# set rank for each message_id for Message rows
|
||||
msg = self.pr.fetch_message(message_id=message_id, fail_if_missing=True)
|
||||
msg.rank = rank
|
||||
self.db.add(msg)
|
||||
|
||||
def _calculate_acceptance(self, labels: list[TextLabels]):
|
||||
# calculate acceptance based on spam label
|
||||
return np.mean([1 - l.labels[protocol_schema.TextLabel.spam] for l in labels])
|
||||
@@ -618,7 +636,7 @@ class TreeManager:
|
||||
qry = (
|
||||
self.db.query(Message)
|
||||
.select_from(MessageTreeState)
|
||||
.outerjoin(Message, MessageTreeState.message_tree_id == Message.message_tree_id)
|
||||
.join(Message, MessageTreeState.message_tree_id == Message.message_tree_id)
|
||||
.filter(
|
||||
MessageTreeState.active,
|
||||
MessageTreeState.state == message_tree_state.State.INITIAL_PROMPT_REVIEW,
|
||||
@@ -643,7 +661,7 @@ class TreeManager:
|
||||
qry = (
|
||||
self.db.query(Message)
|
||||
.select_from(MessageTreeState)
|
||||
.outerjoin(Message, MessageTreeState.message_tree_id == Message.message_tree_id)
|
||||
.join(Message, MessageTreeState.message_tree_id == Message.message_tree_id)
|
||||
.filter(
|
||||
MessageTreeState.active,
|
||||
MessageTreeState.state == message_tree_state.State.GROWING,
|
||||
@@ -664,7 +682,7 @@ class TreeManager:
|
||||
SELECT m.parent_id, m.role, COUNT(m.id) children_count, MIN(m.ranking_count) child_min_ranking_count,
|
||||
COUNT(m.id) FILTER (WHERE m.ranking_count >= :num_required_rankings) as completed_rankings
|
||||
FROM message_tree_state mts
|
||||
LEFT JOIN message m ON mts.message_tree_id = m.message_tree_id
|
||||
INNER JOIN message m ON mts.message_tree_id = m.message_tree_id
|
||||
WHERE mts.active -- only consider active trees
|
||||
AND mts.state = :ranking_state -- message tree must be in ranking state
|
||||
AND m.review_result -- must be reviewed
|
||||
@@ -690,15 +708,15 @@ HAVING COUNT(m.id) > 1 and MIN(m.ranking_count) < :num_required_rankings
|
||||
-- find all extendible parent nodes
|
||||
SELECT m.id as parent_id, m.role as parent_role, m.depth, m.message_tree_id, COUNT(c.id) active_children_count
|
||||
FROM message_tree_state mts
|
||||
LEFT JOIN message m ON mts.message_tree_id = m.message_tree_id -- all elements of message tree
|
||||
INNER JOIN message m ON mts.message_tree_id = m.message_tree_id -- all elements of message tree
|
||||
LEFT JOIN message c ON m.id = c.parent_id -- child nodes
|
||||
WHERE mts.active -- only consider active trees
|
||||
AND mts.state = :growing_state -- message tree must be growing
|
||||
AND NOT m.deleted -- ignore deleted messages as parents
|
||||
AND m.depth < mts.max_depth -- ignore leaf nodes as parents
|
||||
AND m.review_result -- parent node must have positive review
|
||||
AND NOT c.deleted -- don't count deleted children
|
||||
AND (c.review_result OR c.review_count < :num_reviews_reply) -- don't count children with negative review but count elements under review
|
||||
AND NOT coalesce(c.deleted, FALSE) -- don't count deleted children
|
||||
AND (c.review_result OR coalesce(c.review_count, 0) < :num_reviews_reply) -- don't count children with negative review but count elements under review
|
||||
GROUP BY m.id, m.role, m.depth, m.message_tree_id, mts.max_children_count
|
||||
HAVING COUNT(c.id) < mts.max_children_count -- below maximum number of children
|
||||
"""
|
||||
@@ -708,7 +726,10 @@ HAVING COUNT(c.id) < mts.max_children_count -- below maximum number of children
|
||||
|
||||
r = self.db.execute(
|
||||
text(self._sql_find_extendible_parents),
|
||||
{"growing_state": message_tree_state.State.GROWING, "num_reviews_reply": self.cfg.num_reviews_reply},
|
||||
{
|
||||
"growing_state": message_tree_state.State.GROWING,
|
||||
"num_reviews_reply": self.cfg.num_reviews_reply,
|
||||
},
|
||||
)
|
||||
return [ExtendibleParentRow.from_orm(x) for x in r.all()]
|
||||
|
||||
@@ -717,8 +738,8 @@ HAVING COUNT(c.id) < mts.max_children_count -- below maximum number of children
|
||||
SELECT m.message_tree_id, mts.goal_tree_size, COUNT(m.id) AS tree_size
|
||||
FROM (
|
||||
SELECT DISTINCT message_tree_id FROM ({_sql_find_extendible_parents}) extendible_parents
|
||||
) trees LEFT JOIN message_tree_state mts ON trees.message_tree_id = mts.message_tree_id
|
||||
LEFT JOIN message m ON mts.message_tree_id = m.message_tree_id
|
||||
) trees INNER JOIN message_tree_state mts ON trees.message_tree_id = mts.message_tree_id
|
||||
INNER JOIN message m ON mts.message_tree_id = m.message_tree_id
|
||||
WHERE NOT m.deleted
|
||||
AND (
|
||||
m.parent_id IS NOT NULL AND (m.review_result OR m.review_count < :num_reviews_reply) -- children
|
||||
@@ -766,7 +787,7 @@ HAVING COUNT(m.id) < mts.goal_tree_size
|
||||
"""Find all initial prompt messages that have no associated message tree state"""
|
||||
qry_missing_tree_states = (
|
||||
self.db.query(Message.id)
|
||||
.join(MessageTreeState, isouter=True)
|
||||
.outerjoin(MessageTreeState, Message.message_tree_id == MessageTreeState.message_tree_id)
|
||||
.filter(
|
||||
Message.parent_id.is_(None),
|
||||
Message.message_tree_id == Message.id,
|
||||
@@ -783,7 +804,7 @@ SELECT p.parent_id, mr.* FROM
|
||||
-- find parents with > 1 children
|
||||
SELECT m.parent_id, m.message_tree_id, COUNT(m.id) children_count
|
||||
FROM message_tree_state mts
|
||||
LEFT JOIN message m ON mts.message_tree_id = m.message_tree_id
|
||||
INNER JOIN message m ON mts.message_tree_id = m.message_tree_id
|
||||
WHERE m.review_result -- must be reviewed
|
||||
AND NOT m.deleted -- not deleted
|
||||
AND m.parent_id IS NOT NULL -- ignore initial prompts
|
||||
@@ -792,8 +813,8 @@ SELECT p.parent_id, mr.* FROM
|
||||
GROUP BY m.parent_id, m.message_tree_id
|
||||
HAVING COUNT(m.id) > 1
|
||||
) as p
|
||||
LEFT JOIN task t ON p.parent_id = t.parent_message_id AND t.done AND (t.payload_type = 'RankPrompterRepliesPayload' OR t.payload_type = 'RankAssistantRepliesPayload')
|
||||
LEFT JOIN message_reaction mr ON mr.task_id = t.id AND mr.payload_type = 'RankingReactionPayload'
|
||||
INNER JOIN task t ON p.parent_id = t.parent_message_id AND t.done AND (t.payload_type = 'RankPrompterRepliesPayload' OR t.payload_type = 'RankAssistantRepliesPayload')
|
||||
INNER JOIN message_reaction mr ON mr.task_id = t.id AND mr.payload_type = 'RankingReactionPayload'
|
||||
"""
|
||||
|
||||
def query_tree_ranking_results(
|
||||
@@ -832,7 +853,7 @@ LEFT JOIN message_reaction mr ON mr.task_id = t.id AND mr.payload_type = 'Rankin
|
||||
state = message_tree_state.State.INITIAL_PROMPT_REVIEW
|
||||
if tree_size > 1:
|
||||
state = message_tree_state.State.GROWING
|
||||
logger.info(f"Inserting missing message tree state for message: {id} ({tree_size=}, {state=})")
|
||||
logger.info(f"Inserting missing message tree state for message: {id} ({tree_size=}, {state=:s})")
|
||||
self._insert_default_state(id, state=state)
|
||||
|
||||
def query_num_active_trees(self) -> int:
|
||||
@@ -903,16 +924,16 @@ if __name__ == "__main__":
|
||||
|
||||
# print("query_num_active_trees", tm.query_num_active_trees())
|
||||
# print("query_incomplete_rankings", tm.query_incomplete_rankings())
|
||||
# print("query_replies_need_review", tm.query_replies_need_review())
|
||||
print("query_replies_need_review", tm.query_replies_need_review())
|
||||
# print("query_incomplete_initial_prompt_reviews", tm.query_prompts_need_review())
|
||||
# print("query_extendible_trees", tm.query_extendible_trees())
|
||||
# print("query_extendible_parents", tm.query_extendible_parents())
|
||||
# print("query_tree_size", tm.query_tree_size(message_tree_id=UUID("bdf434cf-4df5-4b74-949c-a5a157bc3292")))
|
||||
|
||||
print(
|
||||
"query_reviews_for_message",
|
||||
tm.query_reviews_for_message(message_id=UUID("6a444493-0d48-4316-a9f1-7e263f5a2473")),
|
||||
)
|
||||
# print(
|
||||
# "query_reviews_for_message",
|
||||
# tm.query_reviews_for_message(message_id=UUID("6a444493-0d48-4316-a9f1-7e263f5a2473")),
|
||||
# )
|
||||
|
||||
# print("next_task:", tm.next_task())
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
from typing import List
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def head_to_head_votes(ranks: List[List[int]]):
|
||||
tallies = np.zeros((len(ranks[0]), len(ranks[0])))
|
||||
names = sorted(ranks[0])
|
||||
ranks = np.array(ranks)
|
||||
# we want the sorted indices
|
||||
ranks = np.argsort(ranks, axis=1)
|
||||
for i in range(ranks.shape[1]):
|
||||
for j in range(i + 1, ranks.shape[1]):
|
||||
# now count the cases someone voted for i over j
|
||||
over_j = np.sum(ranks[:, i] < ranks[:, j])
|
||||
over_i = np.sum(ranks[:, j] < ranks[:, i])
|
||||
tallies[i, j] = over_j
|
||||
# tallies[i,j] = over_i
|
||||
tallies[j, i] = over_i
|
||||
# tallies[j,i] = over_j
|
||||
return tallies, names
|
||||
|
||||
|
||||
def cycle_detect(pairs):
|
||||
"""Recursively detect cylces by removing condorcet losers until either only one pair is left or condorcet loosers no longer exist
|
||||
This method upholds the invariant that in a ranking for all a,b either a>b or b>a for all a,b.
|
||||
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : False if the pairs do not contain a cycle, True if the pairs contain a cycle
|
||||
|
||||
|
||||
"""
|
||||
# get all condorcet losers (pairs that loose to all other pairs)
|
||||
# idea: filter all losers that are never winners
|
||||
# print("pairs", pairs)
|
||||
if len(pairs) <= 1:
|
||||
return False
|
||||
losers = [c_lose for c_lose in np.unique(pairs[:, 1]) if c_lose not in pairs[:, 0]]
|
||||
if len(losers) == 0:
|
||||
# if we recursively removed pairs, and at some point we did not have
|
||||
# a condorcet loser, that means everything is both a winner and loser,
|
||||
# yielding at least one (winner,loser), (loser,winner) pair
|
||||
return True
|
||||
|
||||
new = []
|
||||
for p in pairs:
|
||||
if p[1] not in losers:
|
||||
new.append(p)
|
||||
return cycle_detect(np.array(new))
|
||||
|
||||
|
||||
def get_winner(pairs):
|
||||
"""
|
||||
This returns _one_ concordant winner.
|
||||
It could be that there are multiple concordant winners, but in our case
|
||||
since we are interested in a ranking, we have to choose one at random.
|
||||
"""
|
||||
losers = np.unique(pairs[:, 1]).astype(int)
|
||||
winners = np.unique(pairs[:, 0]).astype(int)
|
||||
for w in winners:
|
||||
if w not in losers:
|
||||
return w
|
||||
|
||||
|
||||
def get_ranking(pairs):
|
||||
"""
|
||||
Abuses concordance property to get a (not necessarily unqiue) ranking.
|
||||
The lack of uniqueness is due to the potential existence of multiple
|
||||
equally ranked winners. We have to pick one, which is where
|
||||
the non-uniqueness comes from
|
||||
"""
|
||||
if len(pairs) == 1:
|
||||
return list(pairs[0])
|
||||
w = get_winner(pairs)
|
||||
# now remove the winner from the list of pairs
|
||||
p_new = np.array([(a, b) for a, b in pairs if a != w])
|
||||
return [w] + get_ranking(p_new)
|
||||
|
||||
|
||||
def ranked_pairs(ranks: List[List[int]]):
|
||||
"""
|
||||
Expects a list of rankings for an item like:
|
||||
[("w","x","z","y") for _ in range(3)]
|
||||
+ [("w","y","x","z") for _ in range(2)]
|
||||
+ [("x","y","z","w") for _ in range(4)]
|
||||
+ [("x","z","w","y") for _ in range(5)]
|
||||
+ [("y","w","x","z") for _ in range(1)]
|
||||
This code is quite brain melting, but the idea is the following:
|
||||
1. create a head-to-head matrix that tallies up all win-lose combinations of preferences
|
||||
2. take all combinations that win more than they loose and sort those by how often they win
|
||||
3. use that to create an (implicit) directed graph
|
||||
4. recursively extract nodes from the graph that do not have incoming edges
|
||||
5. said recursive list is the ranking
|
||||
"""
|
||||
tallies, names = head_to_head_votes(ranks)
|
||||
tallies = tallies - tallies.T
|
||||
# print(tallies)
|
||||
# note: the resulting tally matrix should be skew-symmetric
|
||||
# order by strength of victory (using tideman's original method, don't think it would make a difference for us)
|
||||
sorted_majorities = []
|
||||
for i in range(len(ranks[0])):
|
||||
for j in range(len(ranks[i])):
|
||||
if tallies[i, j] > 0:
|
||||
sorted_majorities.append((i, j, tallies[i, j]))
|
||||
# we don't explicitly deal with tied majorities here
|
||||
sorted_majorities = np.array(sorted(sorted_majorities, key=lambda x: x[2], reverse=True))
|
||||
# now do lock ins
|
||||
lock_ins = []
|
||||
for (x, y, _) in sorted_majorities:
|
||||
# invariant: lock_ins has no cycles here
|
||||
lock_ins.append((x, y))
|
||||
# print("lock ins are now",np.array(lock_ins))
|
||||
if cycle_detect(np.array(lock_ins)):
|
||||
# print("backup: cycle detected")
|
||||
# if there's a cycle, delete the new addition and continue
|
||||
lock_ins = lock_ins[:-1]
|
||||
# now simply return all winners in order, and attach the losers
|
||||
# to the back. This is because the overall loser might not be unique
|
||||
# and (by concordance property) may never exist in any winning set to begin with.
|
||||
# (otherwise he would either not be the loser, or cycles exist!)
|
||||
# Since there could be multiple overall losers, we just return them in any order
|
||||
# as we are unable to find a closer ranking
|
||||
numerical_ranks = np.array(get_ranking(np.array(lock_ins))).astype(int)
|
||||
conversion = [names[n] for n in numerical_ranks]
|
||||
return conversion
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ranks = (
|
||||
[("w", "x", "z", "y") for _ in range(1)]
|
||||
+ [("w", "y", "x", "z") for _ in range(2)]
|
||||
# + [("x","y","z","w") for _ in range(4)]
|
||||
+ [("x", "z", "w", "y") for _ in range(5)]
|
||||
+ [("y", "w", "x", "z") for _ in range(1)]
|
||||
# [("y","z","w","x") for _ in range(1000)]
|
||||
)
|
||||
rp = ranked_pairs(ranks)
|
||||
print(rp)
|
||||
@@ -8,15 +8,29 @@ This page lists research papers that are relevant to the project.
|
||||
- Generating Text From Language Models
|
||||
- Automatically Generating Instruction Data for Training
|
||||
- Uncertainty Estimation of Language Model Outputs
|
||||
- Evidence-Guided Text Generation
|
||||
- Reward Model Optimization
|
||||
- Dialogue-Oriented RLHF
|
||||
- Reduce Harms in Language Models
|
||||
|
||||
## Reinforcement Learning from Human Feedback <a name="reinforcement-learning-from-human-feedback"></a>
|
||||
## Reinforcement Learning from Human Feedback
|
||||
|
||||
Reinforcement Learning from Human Feedback (RLHF) is a method for fine-tuning a
|
||||
generative language models based on a reward model that is learned from human
|
||||
preference data. This method facilitates the learning of instruction-tuned
|
||||
models, among other things.
|
||||
|
||||
### Learning to summarize from human feedback [[ArXiv](https://arxiv.org/pdf/2009.01325.pdf)], [[Github](https://github.com/openai/summarize-from-feedback)]
|
||||
### Fine-Tuning Language Models from Human Preferences [[ArXiv](https://arxiv.org/abs/1909.08593)], [[GitHub](https://github.com/openai/lm-human-preferences)]
|
||||
|
||||
> In this paper, we build on advances in generative pretraining of language
|
||||
> models to apply reward learning to four natural language tasks: continuing
|
||||
> text with positive sentiment or physically descriptive language, and
|
||||
> summarization tasks on the TL;DR and CNN/Daily Mail datasets. For stylistic
|
||||
> continuation we achieve good results with only 5,000 comparisons evaluated by
|
||||
> humans. For summarization, models trained with 60,000 comparisons copy whole
|
||||
> sentences from the input but skip irrelevant preamble.
|
||||
|
||||
### Learning to summarize from human feedback [[ArXiv](https://arxiv.org/abs/2009.01325)], [[GitHub](https://github.com/openai/summarize-from-feedback)]
|
||||
|
||||
> In this work, we show that it is possible to significantly improve summary
|
||||
> quality by training a model to optimize for human preferences. We collect a
|
||||
@@ -24,7 +38,18 @@ models, among other things.
|
||||
> model to predict the human-preferred summary, and use that model as a reward
|
||||
> function to fine-tune a summarization policy using reinforcement learning.
|
||||
|
||||
### Training language models to follow instructions with human feedback [[ArXiv](https://arxiv.org/pdf/2203.02155.pdf)]
|
||||
### Recursively Summarizing Books with Human Feedback [[ArXiv](https://arxiv.org/abs/2109.10862)]
|
||||
|
||||
> Our method combines learning from human feedback with recursive task
|
||||
> decomposition: we use models trained on smaller parts of the task to assist
|
||||
> humans in giving feedback on the broader task. We collect a large volume of
|
||||
> demonstrations and comparisons from human labelers. Our resulting model
|
||||
> generates sensible summaries of entire books, even matching the quality of
|
||||
> human-written summaries in a few cases (∼5% of books). We achieve
|
||||
> state-of-the-art results on the recent BookSum dataset for book-length
|
||||
> summarization. We release datasets of samples from our model.
|
||||
|
||||
### Training language models to follow instructions with human feedback [[ArXiv](https://arxiv.org/abs/2203.02155)]
|
||||
|
||||
> Starting with a set of labeler-written prompts and prompts submitted through
|
||||
> the OpenAI API, we collect a dataset of labeler demonstrations of the desired
|
||||
@@ -33,7 +58,7 @@ models, among other things.
|
||||
> fine-tune this supervised model using reinforcement learning from human
|
||||
> feedback.
|
||||
|
||||
### Training a Helpful and Harmless Assistant with Reinforcement Learning from Human Feedback [[ArXiv](https://arxiv.org/pdf/2204.05862.pdf)]
|
||||
### Training a Helpful and Harmless Assistant with Reinforcement Learning from Human Feedback [[ArXiv](https://arxiv.org/abs/2204.05862)]
|
||||
|
||||
> We apply preference modeling and reinforcement learning from human feedback
|
||||
> (RLHF) to finetune language models to act as helpful and harmless assistants.
|
||||
@@ -41,6 +66,31 @@ models, among other things.
|
||||
> evaluations, and is fully compatible with training for specialized skills such
|
||||
> as python coding and summarization.
|
||||
|
||||
### Self-critiquing models for assisting human evaluators [[ArXiv](https://arxiv.org/abs/2206.05802)]
|
||||
|
||||
> We fine-tune large language models to write natural language critiques
|
||||
> (natural language critical comments) using behavioral cloning. On a
|
||||
> topic-based summarization task, critiques written by our models help humans
|
||||
> find flaws in summaries that they would have otherwise missed. We study
|
||||
> scaling properties of critiquing with both topic-based summarization and
|
||||
> synthetic tasks. Finally, we motivate and introduce a framework for comparing
|
||||
> critiquing ability to generation and discrimination ability. These results are
|
||||
> a proof of concept for using AI-assisted human feedback to scale the
|
||||
> supervision of machine learning systems to tasks that are difficult for humans
|
||||
> to evaluate directly. We release our training datasets.
|
||||
|
||||
### Is Reinforcement Learning (Not) for Natural Language Processing?: Benchmarks, Baselines, and Building Blocks for Natural Language Policy Optimization [[ArXiv](https://arxiv.org/abs/2210.01241)]
|
||||
|
||||
> We tackle the problem of aligning pre-trained large language models (LMs) with
|
||||
> human preferences. We present the GRUE (General Reinforced-language
|
||||
> Understanding Evaluation) benchmark, a set of 6 language generation tasks
|
||||
> which are supervised by reward functions which capture automated measures of
|
||||
> human preference. Finally, we introduce an easy-to-use, performant RL
|
||||
> algorithm, NLPO (Natural Language Policy Optimization) that learns to
|
||||
> effectively reduce the combinatorial action space in language generation. We
|
||||
> show that RL techniques are generally better than supervised methods at
|
||||
> aligning LMs to human preferences.
|
||||
|
||||
## Generating Text From Language Models
|
||||
|
||||
A language model generates output text token by token, autoregressively. The
|
||||
@@ -48,7 +98,7 @@ large search space of this task requires some method of narrowing down the set
|
||||
of tokens to be considered in each step. This method, in turn, has a big impact
|
||||
on the quality of the resulting text.
|
||||
|
||||
### RANKGEN: Improving Text Generation with Large Ranking Models [[ArXiv](https://arxiv.org/pdf/2205.09726.pdf)], [[Github](https://github.com/martiansideofthemoon/rankgen)]
|
||||
### RANKGEN: Improving Text Generation with Large Ranking Models [[ArXiv](https://arxiv.org/abs/2205.09726)], [[GitHub](https://github.com/martiansideofthemoon/rankgen)]
|
||||
|
||||
> Given an input sequence (or prefix), modern language models often assign high
|
||||
> probabilities to output sequences that are repetitive, incoherent, or
|
||||
@@ -65,7 +115,7 @@ annotated data for the purpose of training
|
||||
[instruction-aligned](https://openai.com/blog/instruction-following/) language
|
||||
models.
|
||||
|
||||
### SELF-INSTRUCT: Aligning Language Model with Self Generated Instructions [[ArXiv](https://arxiv.org/pdf/2212.10560.pdf)], [[Github](https://github.com/yizhongw/self-instruct)].
|
||||
### SELF-INSTRUCT: Aligning Language Model with Self Generated Instructions [[ArXiv](https://arxiv.org/abs/2212.10560)], [[GitHub](https://github.com/yizhongw/self-instruct)].
|
||||
|
||||
> We introduce SELF-INSTRUCT, a framework for improving the
|
||||
> instruction-following capabilities of pretrained language models by
|
||||
@@ -76,7 +126,7 @@ models.
|
||||
> SuperNaturalInstructions, on par with the performance of InstructGPT-0011,
|
||||
> which is trained with private user data and human annotations.
|
||||
|
||||
### Tuning Language Models with (Almost) No Human Labor. [[ArXiv](https://arxiv.org/pdf/2212.09689.pdf)], [[Github](https://github.com/orhonovich/unnatural-instructions)].
|
||||
### Tuning Language Models with (Almost) No Human Labor. [[ArXiv](https://arxiv.org/abs/2212.09689)], [[GitHub](https://github.com/orhonovich/unnatural-instructions)].
|
||||
|
||||
> In this work, we introduce Unnatural Instructions: a large dataset of creative
|
||||
> and diverse instructions, collected with virtually no human labor. We collect
|
||||
@@ -91,7 +141,7 @@ models.
|
||||
|
||||
## Uncertainty Estimation of Language Model Outputs
|
||||
|
||||
### Teaching models to express their uncertainty in words [[Arxiv](https://arxiv.org/pdf/2205.14334.pdf)]
|
||||
### Teaching models to express their uncertainty in words [[ArXiv](https://arxiv.org/abs/2205.14334)]
|
||||
|
||||
> We show that a GPT-3 model can learn to express uncertainty about its own
|
||||
> answers in natural language -- without use of model logits. When given a
|
||||
@@ -100,3 +150,69 @@ models.
|
||||
> are well calibrated. The model also remains moderately calibrated under
|
||||
> distribution shift, and is sensitive to uncertainty in its own answers, rather
|
||||
> than imitating human examples.
|
||||
|
||||
## Evidence-Guided Text Generation
|
||||
|
||||
### WebGPT: Browser-assisted question-answering with human feedback [[ArXiv](https://arxiv.org/abs/2112.09332)]
|
||||
|
||||
> We fine-tune GPT-3 to answer long-form questions using a text-based
|
||||
> web-browsing environment, which allows the model to search and navigate the
|
||||
> web. We are able to train models on the task using imitation learning, and
|
||||
> then optimize answer quality with human feedback. Models must collect
|
||||
> references while browsing in support of their answers. Our best model is
|
||||
> obtained by fine-tuning GPT-3 using behavior cloning, and then performing
|
||||
> rejection sampling against a reward model.
|
||||
|
||||
### Teaching language models to support answers with verified quotes [[ArXiv](https://arxiv.org/abs/2203.11147)]
|
||||
|
||||
> In this work we use RLHF to train "open-book" QA models that generate answers
|
||||
> whilst also citing specific evidence for their claims, which aids in the
|
||||
> appraisal of correctness. Supporting evidence is drawn from multiple documents
|
||||
> found via a search engine, or from a single user-provided document. However,
|
||||
> analysis on the adversarial TruthfulQA dataset shows why citation is only one
|
||||
> part of an overall strategy for safety and trustworthiness: not all claims
|
||||
> supported by evidence are true.
|
||||
|
||||
## Reward Model Optimization
|
||||
|
||||
### Scaling Laws for Reward Model Overoptimization [[ArXiv](https://arxiv.org/abs/2210.10760)], [[Preceding Blogpost](https://openai.com/blog/measuring-goodharts-law/)]
|
||||
|
||||
> In this work, we use a synthetic setup in which a fixed "gold-standard" reward
|
||||
> model plays the role of humans, providing labels used to train a proxy reward
|
||||
> model. We study how the gold reward model score changes as we optimize against
|
||||
> the proxy reward model using either reinforcement learning or best-of-n
|
||||
> sampling. We study the effect on this relationship of the size of the reward
|
||||
> model dataset. We explore the implications of these empirical results for
|
||||
> theoretical considerations in AI alignment.
|
||||
|
||||
## Dialogue-Oriented RLHF
|
||||
|
||||
### Dynamic Planning in Open-Ended Dialogue using Reinforcement Learning [[ArXiv](https://arxiv.org/abs/2208.02294)]
|
||||
|
||||
> Building automated agents that can carry on rich open-ended conversations with
|
||||
> humans "in the wild" remains a formidable challenge. In this work we develop a
|
||||
> real-time, open-ended dialogue system that uses reinforcement learning (RL) to
|
||||
> power a bot's conversational skill at scale. Trained using crowd-sourced data,
|
||||
> our novel system is able to substantially exceeds several metrics of interest
|
||||
> in a live experiment with real users of the Google Assistant.
|
||||
|
||||
### Improving alignment of dialogue agents via targeted human judgements [[ArXiv](https://arxiv.org/abs/2209.14375)]
|
||||
|
||||
> We present Sparrow, an information-seeking dialogue agent trained to be more
|
||||
> helpful, correct, and harmless compared to prompted language model baselines
|
||||
> First, to make our agent more helpful and harmless, we break down the
|
||||
> requirements for good dialogue into natural language rules the agent should
|
||||
> followy. Second, our agent provides evidence from sources supporting factual
|
||||
> claims when collecting preference judgements over model statements.Finally, we
|
||||
> conduct extensive analyses showing that though our model learns to follow our
|
||||
> rules it can exhibit distributional biases.
|
||||
|
||||
## Reduce Harms in Language Models
|
||||
|
||||
### Red Teaming Language Models to Reduce Harms: Methods, Scaling Behaviors, and Lessons Learned [[ArXiv](https://arxiv.org/abs/2209.07858)]
|
||||
|
||||
> We investigate scaling behaviors for red teaming. We find that the RLHF models
|
||||
> are increasingly difficult to red team as they scale, and we find a flat trend
|
||||
> with scale for the other model types. We exhaustively describe our
|
||||
> instructions, processes, statistical methodologies, and uncertainty about red
|
||||
> teaming.
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
"""Simple REPL frontend."""
|
||||
|
||||
import http
|
||||
import random
|
||||
|
||||
import requests
|
||||
import typer
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
|
||||
# debug constants
|
||||
USER = {"id": "1234", "display_name": "John Doe", "auth_method": "local"}
|
||||
|
||||
|
||||
def _random_message_id():
|
||||
return str(random.randint(1000, 9999))
|
||||
|
||||
|
||||
def _render_message(message: dict) -> str:
|
||||
"""Render a message to the user."""
|
||||
if message["is_assistant"]:
|
||||
return f"Assistant: {message['text']}"
|
||||
return f"Prompter: {message['text']}"
|
||||
|
||||
|
||||
@app.command()
|
||||
def main(backend_url: str = "http://127.0.0.1:8080", api_key: str = "1234"):
|
||||
"""automates tasks"""
|
||||
|
||||
def _post(path: str, json: dict) -> dict:
|
||||
response = requests.post(f"{backend_url}{path}", json=json, headers={"X-API-Key": api_key})
|
||||
response.raise_for_status()
|
||||
if response.status_code == http.HTTPStatus.NO_CONTENT:
|
||||
return None
|
||||
return response.json()
|
||||
|
||||
def gen_random_text():
|
||||
return " ".join([random.choice(["hello", "world", "foo", "bar"]) for _ in range(10)])
|
||||
|
||||
def gen_random_ranking(messages):
|
||||
"""rank messages randomly and return list of indexes in order of rank randomly"""
|
||||
print("Ranking")
|
||||
print(messages)
|
||||
print(len(messages))
|
||||
ranks = [i for i in range(len(messages))]
|
||||
shuffled = random.shuffle(ranks)
|
||||
print(ranks)
|
||||
print(shuffled)
|
||||
return ranks
|
||||
|
||||
tasks = [_post("/api/v1/tasks/", {"type": "random", "user": USER})]
|
||||
q = 0
|
||||
while tasks:
|
||||
task = tasks.pop(0)
|
||||
print(task)
|
||||
|
||||
match (task["type"]):
|
||||
case "initial_prompt":
|
||||
typer.echo("Please provide an initial prompt to the assistant.")
|
||||
if task["hint"]:
|
||||
typer.echo(f"Hint: {task['hint']}")
|
||||
# acknowledge task
|
||||
message_id = _random_message_id()
|
||||
_post(f"/api/v1/tasks/{task['id']}/ack", {"message_id": message_id})
|
||||
|
||||
prompt = gen_random_text()
|
||||
user_message_id = _random_message_id()
|
||||
# send interaction
|
||||
new_task = _post(
|
||||
"/api/v1/tasks/interaction",
|
||||
{
|
||||
"type": "text_reply_to_message",
|
||||
"message_id": message_id,
|
||||
"task_id": task["id"],
|
||||
"user_message_id": user_message_id,
|
||||
"text": prompt,
|
||||
"user": USER,
|
||||
},
|
||||
)
|
||||
tasks.append(new_task)
|
||||
|
||||
case "label_initial_prompt":
|
||||
typer.echo("Label the following prompt:")
|
||||
typer.echo(task["prompt"])
|
||||
# acknowledge task
|
||||
message_id = _random_message_id()
|
||||
_post(f"/api/v1/tasks/{task['id']}/ack", {"message_id": message_id})
|
||||
|
||||
valid_labels = task["valid_labels"]
|
||||
|
||||
labels_dict = None
|
||||
if task["mode"] == "simple" and len(valid_labels) == 1:
|
||||
answer = random.choice([True, False])
|
||||
labels_dict = {valid_labels[0]: 1 if answer else 0}
|
||||
else:
|
||||
while labels_dict is None:
|
||||
labels = random.sample(valid_labels, random.randint(1, len(valid_labels)))
|
||||
|
||||
if all([label in valid_labels for label in labels]):
|
||||
labels_dict = {label: "1" if label in labels else "0" for label in valid_labels}
|
||||
else:
|
||||
invalid_labels = [label for label in labels if label not in valid_labels]
|
||||
typer.echo(f"Invalid labels: {', '.join(invalid_labels)}. Valid: {', '.join(valid_labels)}")
|
||||
|
||||
# send labels
|
||||
new_task = _post(
|
||||
"/api/v1/tasks/interaction",
|
||||
{
|
||||
"type": "text_labels",
|
||||
"message_id": task["message_id"],
|
||||
"task_id": task["id"],
|
||||
"text": task["prompt"],
|
||||
"labels": labels_dict,
|
||||
"user": USER,
|
||||
},
|
||||
)
|
||||
tasks.append(new_task)
|
||||
case "prompter_reply":
|
||||
# acknowledge task
|
||||
message_id = _random_message_id()
|
||||
user_message_id = _random_message_id()
|
||||
_post(f"/api/v1/tasks/{task['id']}/ack", {"message_id": message_id})
|
||||
# send interaction
|
||||
new_task = _post(
|
||||
"/api/v1/tasks/interaction",
|
||||
{
|
||||
"type": "text_reply_to_message",
|
||||
"message_id": message_id,
|
||||
"task_id": task["id"],
|
||||
"user_message_id": user_message_id,
|
||||
"text": gen_random_text(),
|
||||
"user": USER,
|
||||
},
|
||||
)
|
||||
tasks.append(new_task)
|
||||
|
||||
case "assistant_reply":
|
||||
# acknowledge task
|
||||
message_id = _random_message_id()
|
||||
user_message_id = _random_message_id()
|
||||
_post(f"/api/v1/tasks/{task['id']}/ack", {"message_id": message_id})
|
||||
# send interaction
|
||||
new_task = _post(
|
||||
"/api/v1/tasks/interaction",
|
||||
{
|
||||
"type": "text_reply_to_message",
|
||||
"message_id": message_id,
|
||||
"task_id": task["id"],
|
||||
"user_message_id": user_message_id,
|
||||
"text": gen_random_text(),
|
||||
"user": USER,
|
||||
},
|
||||
)
|
||||
tasks.append(new_task)
|
||||
|
||||
case "rank_prompter_replies" | "rank_assistant_replies":
|
||||
# acknowledge task
|
||||
message_id = _random_message_id()
|
||||
user_message_id = _random_message_id()
|
||||
_post(f"/api/v1/tasks/{task['id']}/ack", {"message_id": message_id})
|
||||
# send interaction
|
||||
ranking = gen_random_ranking(task["replies"])
|
||||
print(ranking)
|
||||
new_task = _post(
|
||||
"/api/v1/tasks/interaction",
|
||||
{
|
||||
"type": "message_ranking",
|
||||
"message_id": message_id,
|
||||
"task_id": task["id"],
|
||||
"ranking": ranking,
|
||||
"user": USER,
|
||||
},
|
||||
)
|
||||
tasks.append(new_task)
|
||||
|
||||
case "rank_initial_prompts":
|
||||
# acknowledge task
|
||||
message_id = _random_message_id()
|
||||
user_message_id = _random_message_id()
|
||||
_post(f"/api/v1/tasks/{task['id']}/ack", {"message_id": message_id})
|
||||
# send interaction
|
||||
ranking = gen_random_ranking(task["prompots"])
|
||||
new_task = _post(
|
||||
"/api/v1/tasks/interaction",
|
||||
{
|
||||
"type": "message_ranking",
|
||||
"message_id": message_id,
|
||||
"ranking": ranking,
|
||||
"user": USER,
|
||||
},
|
||||
)
|
||||
tasks.append(new_task)
|
||||
|
||||
case "label_prompter_reply" | "label_assistant_reply":
|
||||
# acknowledge task
|
||||
typer.echo("Here is the conversation so far:")
|
||||
for message in task["conversation"]["messages"]:
|
||||
typer.echo(_render_message(message))
|
||||
|
||||
typer.echo("Label the following reply:")
|
||||
typer.echo(task["reply"])
|
||||
message_id = _random_message_id()
|
||||
user_message_id = _random_message_id()
|
||||
_post(f"/api/v1/tasks/{task['id']}/ack", {"message_id": message_id})
|
||||
valid_labels = task["valid_labels"]
|
||||
|
||||
labels_dict = None
|
||||
if task["mode"] == "simple" and len(valid_labels) == 1:
|
||||
answer = random.choice([True, False])
|
||||
labels_dict = {valid_labels[0]: 1 if answer else 0}
|
||||
else:
|
||||
while labels_dict is None:
|
||||
labels = random.sample(valid_labels, random.randint(1, len(valid_labels)))
|
||||
|
||||
if all([label in valid_labels for label in labels]):
|
||||
labels_dict = {label: "1" if label in labels else "0" for label in valid_labels}
|
||||
else:
|
||||
invalid_labels = [label for label in labels if label not in valid_labels]
|
||||
typer.echo(f"Invalid labels: {', '.join(invalid_labels)}. Valid: {', '.join(valid_labels)}")
|
||||
# send interaction
|
||||
new_task = _post(
|
||||
"/api/v1/tasks/interaction",
|
||||
{
|
||||
"type": "text_labels",
|
||||
"message_id": task["message_id"],
|
||||
"task_id": task["id"],
|
||||
"text": task["reply"],
|
||||
"labels": labels_dict,
|
||||
"user": USER,
|
||||
},
|
||||
)
|
||||
tasks.append(new_task)
|
||||
case "task_done":
|
||||
typer.echo("Task done!")
|
||||
# rerun with new task slected from above cases
|
||||
# add a new task
|
||||
q += 1
|
||||
if q == 10:
|
||||
typer.echo("Task done!")
|
||||
break
|
||||
tasks = [_post("/api/v1/tasks/", {"type": "random", "user": USER})]
|
||||
#
|
||||
case _:
|
||||
typer.echo(f"Unknown task type {task['type']}")
|
||||
# rerun with new task slected from above cases
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
import React, { ReactNode } from "react";
|
||||
|
||||
const killEvent = (e) => e.stopPropagation();
|
||||
|
||||
export const CollapsableText = ({
|
||||
text,
|
||||
maxLength = 220,
|
||||
@@ -44,8 +46,9 @@ export const CollapsableText = ({
|
||||
</Button>
|
||||
</span>
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="xl" scrollBehavior={"inside"}>
|
||||
<ModalOverlay style={{ width: "100%", height: "100%" }}>
|
||||
<ModalContent maxH="400">
|
||||
{/* we kill the event here to disable drag and drop, since it is in the same container */}
|
||||
<ModalOverlay onMouseDown={killEvent}>
|
||||
<ModalContent alignItems="center">
|
||||
<ModalHeader>Full Text</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>{text}</ModalBody>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Box, Divider } from "@chakra-ui/react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export function SlimFooter() {
|
||||
return (
|
||||
<footer>
|
||||
<Box>
|
||||
<Divider />
|
||||
<Box display="flex" gap="4" flexDir="column" alignItems="center" my="8">
|
||||
<Box display="flex" alignItems="center">
|
||||
<Link href="/" aria-label="Home" className="flex items-center gap-1">
|
||||
<Image src="/images/logos/logo.svg" className="mx-auto object-fill" width="48" height="48" alt="logo" />
|
||||
</Link>
|
||||
</Box>
|
||||
<nav>
|
||||
<Box display="flex" gap="5" fontSize="xs" color="blue.500">
|
||||
<FooterLink href="/privacy-policy" label="Privacy Policy" />
|
||||
<FooterLink href="/terms-of-service" label="Terms of Service" />
|
||||
<FooterLink href="https://github.com/LAION-AI/Open-Assistant" label="Github" />
|
||||
<FooterLink href="https://ykilcher.com/open-assistant-discord" label="Discord" />
|
||||
<FooterLink href="https://projects.laion.ai/Open-Assistant/" label="Docs" />
|
||||
</Box>
|
||||
</nav>
|
||||
</Box>
|
||||
</Box>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
const FooterLink = ({ href, label }: { href: string; label: string }) =>
|
||||
useMemo(
|
||||
() => (
|
||||
<Link
|
||||
href={href}
|
||||
rel="noopener noreferrer nofollow"
|
||||
target="_blank"
|
||||
aria-label={label}
|
||||
className="hover:underline underline-offset-2"
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
),
|
||||
[href, label]
|
||||
);
|
||||
@@ -0,0 +1,43 @@
|
||||
import { Box, Divider, Text, useColorMode } from "@chakra-ui/react";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
export function WelcomeCard() {
|
||||
const { colorMode } = useColorMode();
|
||||
const backgroundColor = colorMode === "light" ? "white" : "gray.700";
|
||||
const titleColor = colorMode === "light" ? "blue.500" : "blue.300";
|
||||
|
||||
const { data: session } = useSession();
|
||||
|
||||
if (!session) {
|
||||
return <></>;
|
||||
}
|
||||
if (session && session.user && session.user.isNew)
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
bgGradient="linear(to-r, blue.300, purple.500)"
|
||||
borderRadius="xl"
|
||||
p="1px"
|
||||
shadow="base"
|
||||
position="relative"
|
||||
>
|
||||
<Box bg={backgroundColor} borderRadius="xl" p="6" pt="4" pr="12">
|
||||
<Box pb="2">
|
||||
<Text as="h1" fontWeight="extrabold" fontSize="3xl" color={titleColor}>
|
||||
Welcome, {session.user.name || "Contributor"}!
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box>
|
||||
<Text>
|
||||
Open Assistant is an open-source AI assistant that uses and trains advanced language models to
|
||||
understand and respond to humans.
|
||||
</Text>
|
||||
<Divider my="4" />
|
||||
<Text>Complete tasks to help train the model and earn points.</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export { LeaderboardTable } from "./LeaderboardTable";
|
||||
export { TaskOption } from "./TaskOption";
|
||||
export { WelcomeCard } from "./WelcomeCard";
|
||||
|
||||
@@ -26,7 +26,7 @@ import { useEffect, useReducer } from "react";
|
||||
import { FiAlertCircle } from "react-icons/fi";
|
||||
import { get, post } from "src/lib/api";
|
||||
import { Message } from "src/types/Conversation";
|
||||
import { colors } from "styles/Theme/colors";
|
||||
import { colors } from "src/styles/Theme/colors";
|
||||
import useSWR from "swr";
|
||||
import useSWRMutation from "swr/mutation";
|
||||
|
||||
@@ -105,6 +105,10 @@ export const FlaggableElement = (props: FlaggableElementProps) => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
if (!data) {
|
||||
updateReport({ type: "load_labels", labels: [] });
|
||||
return;
|
||||
}
|
||||
const { valid_labels } = data;
|
||||
updateReport({ type: "load_labels", labels: valid_labels });
|
||||
}, [data, isLoading]);
|
||||
|
||||
@@ -1,40 +1,72 @@
|
||||
import { useColorMode } from "@chakra-ui/react";
|
||||
import { Box, Divider, Flex, Text, useColorMode } from "@chakra-ui/react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export function Footer() {
|
||||
const { colorMode } = useColorMode();
|
||||
const bgColorClass = colorMode === "light" ? "bg-transparent" : "bg-gray-800";
|
||||
const borderClass = colorMode === "light" ? "border-slate-200" : "border-transparent";
|
||||
const backgroundColor = colorMode === "light" ? "white" : "gray.800";
|
||||
const textColor = colorMode === "light" ? "black" : "gray.300";
|
||||
|
||||
return (
|
||||
<footer className={bgColorClass}>
|
||||
<div className={`flex mx-auto max-w-7xl justify-between border-t p-10 ${borderClass}`}>
|
||||
<div className="flex items-center pr-8">
|
||||
<Link href="/" aria-label="Home" className="flex items-center">
|
||||
<Image src="/images/logos/logo.svg" className="mx-auto object-fill" width="52" height="52" alt="logo" />
|
||||
</Link>
|
||||
<footer>
|
||||
<Box bg={backgroundColor}>
|
||||
<Divider />
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection={["column", "row"]}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap="6"
|
||||
p="8"
|
||||
pb={["14", "8"]}
|
||||
w="full"
|
||||
mx="auto"
|
||||
maxWidth="7xl"
|
||||
>
|
||||
<Flex alignItems="center">
|
||||
<Box pr="2">
|
||||
<Link href="/" aria-label="Home">
|
||||
<Image src="/images/logos/logo.svg" width="52" height="52" alt="logo" />
|
||||
</Link>
|
||||
</Box>
|
||||
|
||||
<div className="ml-2">
|
||||
<p className="text-base font-bold">Open Assistant</p>
|
||||
<p className="text-sm">Conversational AI for everyone.</p>
|
||||
</div>
|
||||
</div>
|
||||
<Box>
|
||||
<Text fontSize="md" fontWeight="bold">
|
||||
Open Assistant
|
||||
</Text>
|
||||
<Text fontSize="sm" color="gray.500">
|
||||
Conversational AI for everyone.
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
<nav className="grid grid-cols-2 gap-20 leading-5 text-sm">
|
||||
<div className="flex flex-col">
|
||||
<b className="pb-1">Legal</b>
|
||||
<FooterLink href="/privacy-policy" label="Privacy Policy" />
|
||||
<FooterLink href="/terms-of-service" label="Terms of Service" />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<b className="pb-1">Connect</b>
|
||||
<FooterLink href="https://github.com/LAION-AI/Open-Assistant" label="Github" />
|
||||
<FooterLink href="https://ykilcher.com/open-assistant-discord" label="Discord" />
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<nav>
|
||||
<Box display="flex" flexDirection={["column", "row"]} gap={["6", "14"]} fontSize="sm">
|
||||
<Flex direction="column" alignItems={["center", "start"]}>
|
||||
<Text fontWeight="bold" color={textColor}>
|
||||
Legal
|
||||
</Text>
|
||||
<FooterLink href="/privacy-policy" label="Privacy Policy" />
|
||||
<FooterLink href="/terms-of-service" label="Terms of Service" />
|
||||
</Flex>
|
||||
<Flex direction="column" alignItems={["center", "start"]}>
|
||||
<Text fontWeight="bold" color={textColor}>
|
||||
Connect
|
||||
</Text>
|
||||
<FooterLink href="https://github.com/LAION-AI/Open-Assistant" label="Github" />
|
||||
<FooterLink href="https://ykilcher.com/open-assistant-discord" label="Discord" />
|
||||
</Flex>
|
||||
<Flex direction="column" alignItems={["center", "start"]}>
|
||||
<Text fontWeight="bold" color={textColor}>
|
||||
About
|
||||
</Text>
|
||||
<FooterLink href="https://projects.laion.ai/Open-Assistant" label="Docs" />
|
||||
</Flex>
|
||||
</Box>
|
||||
</nav>
|
||||
</Box>
|
||||
</Box>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -42,14 +74,10 @@ export function Footer() {
|
||||
const FooterLink = ({ href, label }: { href: string; label: string }) =>
|
||||
useMemo(
|
||||
() => (
|
||||
<Link
|
||||
href={href}
|
||||
rel="noopener noreferrer nofollow"
|
||||
target="_blank"
|
||||
aria-label={label}
|
||||
className="hover:underline underline-offset-2"
|
||||
>
|
||||
{label}
|
||||
<Link href={href} rel="noopener noreferrer nofollow" target="_blank" aria-label={label}>
|
||||
<Text color="blue.500" textUnderlineOffset={2} _hover={{ textDecoration: "underline" }}>
|
||||
{label}
|
||||
</Text>
|
||||
</Link>
|
||||
),
|
||||
[href, label]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Button, Text, useColorMode } from "@chakra-ui/react";
|
||||
import { Box, Button, Text, Flex } from "@chakra-ui/react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useSession } from "next-auth/react";
|
||||
@@ -13,10 +13,12 @@ function AccountButton() {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<Link href="/auth/signin" aria-label="Home" className="flex items-center">
|
||||
<Button variant="outline" leftIcon={<FaUser />}>
|
||||
Sign in
|
||||
</Button>
|
||||
<Link href="/auth/signin" aria-label="Home">
|
||||
<Flex alignItems="center">
|
||||
<Button variant="outline" leftIcon={<FaUser />}>
|
||||
Sign in
|
||||
</Button>
|
||||
</Flex>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -25,30 +27,25 @@ export function Header(props) {
|
||||
const { data: session } = useSession();
|
||||
const homeURL = session ? "/dashboard" : "/";
|
||||
|
||||
const { colorMode } = useColorMode();
|
||||
const borderClass = props.transparent
|
||||
? ""
|
||||
: colorMode === "light"
|
||||
? "border-b border-gray-400"
|
||||
: "border-b border-zinc-800";
|
||||
return (
|
||||
<nav className={`oa-basic-theme ${borderClass}`}>
|
||||
<Box className="relative z-10 flex justify-between px-4 py-4">
|
||||
<div className="relative z-10 flex items-center gap-10">
|
||||
<Link href={homeURL} aria-label="Home" className="flex items-center">
|
||||
<nav className="oa-basic-theme">
|
||||
<Box display="flex" justifyContent="space-between" p="4">
|
||||
<Link href={homeURL} aria-label="Home">
|
||||
<Flex alignItems="center">
|
||||
<Image src="/images/logos/logo.svg" className="mx-auto object-fill" width="50" height="50" alt="logo" />
|
||||
<Text fontFamily="inter" fontSize="2xl" fontWeight="bold" className="ml-3">
|
||||
<Text fontFamily="inter" fontSize="2xl" fontWeight="bold" ml="3">
|
||||
Open Assistant
|
||||
</Text>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
</Flex>
|
||||
</Link>
|
||||
|
||||
<Flex alignItems="center" gap="4">
|
||||
<Flags authorizedFlags={["flagTest"]}>
|
||||
<div>FlagTest</div>
|
||||
<Text>FlagTest</Text>
|
||||
</Flags>
|
||||
<AccountButton />
|
||||
<UserMenu />
|
||||
</div>
|
||||
</Flex>
|
||||
</Box>
|
||||
</nav>
|
||||
);
|
||||
|
||||
@@ -65,7 +65,7 @@ export function Hero() {
|
||||
<div className="overflow-hidden py-20 sm:py-32 lg:pb-32 xl:pb-36">
|
||||
<Container className="">
|
||||
<div className="lg:grid lg:grid-cols-12 lg:gap-x-8 lg:gap-y-20">
|
||||
<div className="relative z-10 mx-auto max-w-2xl lg:col-span-7 lg:max-w-none lg:pt-6 xl:col-span-6">
|
||||
<div className="relative mx-auto max-w-2xl lg:col-span-7 lg:max-w-none lg:pt-6 xl:col-span-6">
|
||||
<h1 className="text-5xl mb-6 font-bold tracking-tight">Open Assistant</h1>
|
||||
<p
|
||||
className={`bg-gradient-to-r ${fancyTextGradientClasses} mt-8 text-3xl inline bg-clip-text font-display tracking-tight text-transparent`}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// https://nextjs.org/docs/basic-features/layouts
|
||||
|
||||
import { Box, Grid } from "@chakra-ui/react";
|
||||
import type { NextPage } from "next";
|
||||
import { FiBarChart2, FiLayout, FiMessageSquare, FiUsers } from "react-icons/fi";
|
||||
import { Header } from "src/components/Header";
|
||||
|
||||
import { SlimFooter } from "./Dashboard/SlimFooter";
|
||||
import { Footer } from "./Footer";
|
||||
import { SideMenuLayout } from "./SideMenuLayout";
|
||||
|
||||
@@ -28,7 +30,7 @@ export const getTransparentHeaderLayout = (page: React.ReactElement) => (
|
||||
);
|
||||
|
||||
export const getDashboardLayout = (page: React.ReactElement) => (
|
||||
<div className="grid grid-rows-[min-content_1fr_min-content] h-full justify-items-stretch">
|
||||
<Grid templateRows="min-content 1fr" h="full">
|
||||
<Header transparent={true} />
|
||||
<SideMenuLayout
|
||||
menuButtonOptions={[
|
||||
@@ -52,10 +54,14 @@ export const getDashboardLayout = (page: React.ReactElement) => (
|
||||
},
|
||||
]}
|
||||
>
|
||||
{page}
|
||||
<Grid templateRows="1fr min-content" h="full">
|
||||
<Box>{page}</Box>
|
||||
<Box mt="10">
|
||||
<SlimFooter />
|
||||
</Box>
|
||||
</Grid>
|
||||
</SideMenuLayout>
|
||||
<Footer />
|
||||
</div>
|
||||
</Grid>
|
||||
);
|
||||
|
||||
export const getAdminLayout = (page: React.ReactElement) => (
|
||||
|
||||
@@ -8,7 +8,7 @@ import useSWRImmutable from "swr/immutable";
|
||||
const columns = [
|
||||
{
|
||||
Header: "Rank",
|
||||
accessor: (item: LeaderboardEntity, rowIndex: number) => "#" + item.rank,
|
||||
accessor: "rank",
|
||||
style: { width: "90px" },
|
||||
},
|
||||
{
|
||||
|
||||
@@ -51,7 +51,7 @@ const Roadmap = () => {
|
||||
</div>
|
||||
<h4 className="font-bold text-xl text-[#858585] text-center max-w-[10rem]">Growing Up</h4>
|
||||
<ul className="ml-6 md:ml-8 lg:ml-6 space-y-4 text-[#858585] list-disc">
|
||||
<li>Third-Party Extentions</li>
|
||||
<li>Third-Party Extensions</li>
|
||||
<li>Device Control</li>
|
||||
<li>Multi-Modality</li>
|
||||
</ul>
|
||||
|
||||
@@ -12,11 +12,11 @@ export const SideMenuLayout = (props: SideMenuLayoutProps) => {
|
||||
|
||||
return (
|
||||
<Box backgroundColor={colorMode === "light" ? colors.light.bg : colors.dark.bg} className="sm:overflow-hidden">
|
||||
<Box className="sm:flex h-full lg:gap-6">
|
||||
<Box className="p-3 lg:p-6 lg:pr-0">
|
||||
<Box display="flex" flexDirection={["column", "row"]} h="full" gap={["0", "0", "0", "6"]}>
|
||||
<Box p={["3", "3", "3", "6"]} pr={["3", "3", "3", "0"]}>
|
||||
<SideMenu buttonOptions={props.menuButtonOptions} />
|
||||
</Box>
|
||||
<Box className="flex flex-col overflow-y-auto p-3 lg:p-6 lg:pl-0 gap-14 w-full">{props.children}</Box>
|
||||
<Box className="overflow-y-auto p-3 lg:p-6 lg:pl-1 w-full">{props.children}</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ const label_messages: { [label: string]: { description: string; explanation: str
|
||||
explanation: [
|
||||
'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-judgements beyond that.",
|
||||
"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.",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Grid, Slider, SliderFilledTrack, SliderThumb, SliderTrack, useColorMode } from "@chakra-ui/react";
|
||||
import { useId, useState } from "react";
|
||||
import { colors } from "styles/Theme/colors";
|
||||
import { colors } from "src/styles/Theme/colors";
|
||||
|
||||
// TODO: consolidate with FlaggableElement
|
||||
interface LabelSliderGroupProps {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { MessageTable } from "src/components/Messages/MessageTable";
|
||||
import { TrackedTextarea } from "src/components/Survey/TrackedTextarea";
|
||||
import { TwoColumnsWithCards } from "src/components/Survey/TwoColumnsWithCards";
|
||||
import { TaskSurveyProps } from "src/components/Tasks/Task";
|
||||
import { TaskHeader } from "src/components/Tasks/TaskHeader";
|
||||
|
||||
export const CreateTask = ({
|
||||
task,
|
||||
@@ -14,7 +15,6 @@ export const CreateTask = ({
|
||||
}: TaskSurveyProps<{ text: string }>) => {
|
||||
const cardColor = useColorModeValue("gray.50", "gray.800");
|
||||
const titleColor = useColorModeValue("gray.800", "gray.300");
|
||||
const labelColor = useColorModeValue("gray.600", "gray.400");
|
||||
|
||||
const [inputText, setInputText] = useState("");
|
||||
const textChangeHandler = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
@@ -33,14 +33,7 @@ export const CreateTask = ({
|
||||
<div data-cy="task" data-task-type="create-task">
|
||||
<TwoColumnsWithCards>
|
||||
<>
|
||||
<Stack spacing="1">
|
||||
<Text fontSize="xl" fontWeight="bold" color={titleColor}>
|
||||
{taskType.label}
|
||||
</Text>
|
||||
<Text fontSize="md" color={labelColor}>
|
||||
{taskType.overview}
|
||||
</Text>
|
||||
</Stack>
|
||||
<TaskHeader taskType={taskType} />
|
||||
{task.conversation ? (
|
||||
<Box mt="4" borderRadius="lg" bg={cardColor} className="p-3 sm:p-6">
|
||||
<MessageTable messages={task.conversation.messages} />
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { Box, Stack, Text, useColorModeValue } from "@chakra-ui/react";
|
||||
import { Box, useColorModeValue } from "@chakra-ui/react";
|
||||
import { useEffect } from "react";
|
||||
import { MessageTable } from "src/components/Messages/MessageTable";
|
||||
import { Sortable } from "src/components/Sortable/Sortable";
|
||||
import { SurveyCard } from "src/components/Survey/SurveyCard";
|
||||
import { TaskSurveyProps } from "src/components/Tasks/Task";
|
||||
import { TaskHeader } from "src/components/Tasks/TaskHeader";
|
||||
|
||||
export const EvaluateTask = ({
|
||||
task,
|
||||
taskType,
|
||||
isEditable,
|
||||
isDisabled,
|
||||
onReplyChanged,
|
||||
}: TaskSurveyProps<{ ranking: number[] }>) => {
|
||||
const cardColor = useColorModeValue("gray.50", "gray.800");
|
||||
const titleColor = useColorModeValue("gray.800", "gray.300");
|
||||
const labelColor = useColorModeValue("gray.600", "gray.400");
|
||||
|
||||
let messages = [];
|
||||
if (task.conversation) {
|
||||
@@ -36,14 +36,7 @@ export const EvaluateTask = ({
|
||||
<div data-cy="task" data-task-type="evaluate-task">
|
||||
<Box mb="4">
|
||||
<SurveyCard>
|
||||
<Stack spacing="1">
|
||||
<Text fontSize="xl" fontWeight="bold" color={titleColor}>
|
||||
Instructions
|
||||
</Text>
|
||||
<Text fontSize="md" color={labelColor}>
|
||||
Given the following {sortables}, sort them from best to worst, best being first, worst being last.
|
||||
</Text>
|
||||
</Stack>
|
||||
<TaskHeader taskType={taskType} />
|
||||
<Box mt="4" p="6" borderRadius="lg" bg={cardColor}>
|
||||
<MessageTable messages={messages} />
|
||||
</Box>
|
||||
|
||||
+7
-12
@@ -1,5 +1,4 @@
|
||||
import { Box } from "@chakra-ui/react";
|
||||
import { Text, useColorModeValue } from "@chakra-ui/react";
|
||||
import { Box, useColorModeValue } from "@chakra-ui/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MessageView } from "src/components/Messages";
|
||||
import { MessageTable } from "src/components/Messages/MessageTable";
|
||||
@@ -7,6 +6,7 @@ import { LabelRadioGroup } from "src/components/Survey/LabelRadioGroup";
|
||||
import { LabelSliderGroup } from "src/components/Survey/LabelSliderGroup";
|
||||
import { TwoColumnsWithCards } from "src/components/Survey/TwoColumnsWithCards";
|
||||
import { TaskSurveyProps } from "src/components/Tasks/Task";
|
||||
import { TaskHeader } from "src/components/Tasks/TaskHeader";
|
||||
import { TaskType } from "src/types/Task";
|
||||
|
||||
export const LabelTask = ({
|
||||
@@ -19,7 +19,10 @@ export const LabelTask = ({
|
||||
const [sliderValues, setSliderValues] = useState<number[]>(new Array(valid_labels.length).fill(0));
|
||||
|
||||
useEffect(() => {
|
||||
onReplyChanged({ content: { labels: {}, text: task.reply, message_id: task.message_id }, state: "DEFAULT" });
|
||||
onReplyChanged({
|
||||
content: { labels: {}, text: task.reply, message_id: task.message_id },
|
||||
state: "NOT_SUBMITTABLE",
|
||||
});
|
||||
}, [task, onReplyChanged]);
|
||||
|
||||
const onSliderChange = (values: number[]) => {
|
||||
@@ -33,20 +36,12 @@ export const LabelTask = ({
|
||||
};
|
||||
|
||||
const cardColor = useColorModeValue("gray.50", "gray.800");
|
||||
const titleColor = useColorModeValue("gray.800", "gray.300");
|
||||
const labelColor = useColorModeValue("gray.600", "gray.400");
|
||||
|
||||
return (
|
||||
<div data-cy="task" data-task-type="label-task">
|
||||
<TwoColumnsWithCards>
|
||||
<>
|
||||
<Text fontSize="xl" fontWeight="bold" color={titleColor}>
|
||||
{taskType.label}
|
||||
</Text>
|
||||
<Text fontSize="md" color={labelColor}>
|
||||
{taskType.overview}
|
||||
</Text>
|
||||
|
||||
<TaskHeader taskType={taskType} />
|
||||
{task.conversation ? (
|
||||
<Box mt="4" p="6" borderRadius="lg" bg={cardColor}>
|
||||
<MessageTable
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./LabelTask";
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
|
||||
import { Task } from "./Task";
|
||||
|
||||
export default {
|
||||
title: "tasks/Task",
|
||||
component: Task,
|
||||
};
|
||||
|
||||
const Template = ({ frontendId, task, trigger, mutate }) => {
|
||||
return <Task frontendId={frontendId} task={task} trigger={trigger} mutate={mutate} />;
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
frontendId: "1234",
|
||||
task: {
|
||||
conversation: [],
|
||||
id: "1234-4321",
|
||||
mandatory_labels: ["spam"],
|
||||
message_id: "772f843e-f740-4aad-a44f-e3cf0260692c",
|
||||
reply: "1231231231",
|
||||
type: "label_prompter_reply",
|
||||
valid_labels: ["spam", "fails_task"],
|
||||
},
|
||||
trigger: (id, update_type, content) => {
|
||||
console.log(content);
|
||||
},
|
||||
mutate: () => {
|
||||
console.log("mutate");
|
||||
},
|
||||
};
|
||||
@@ -27,6 +27,8 @@ export const Task = ({ frontendId, task, trigger, mutate }) => {
|
||||
const replyContent = useRef<TaskContent>(null);
|
||||
const [showUnchangedWarning, setShowUnchangedWarning] = useState(false);
|
||||
|
||||
const rootEl = useRef<HTMLDivElement>(null);
|
||||
|
||||
const taskType = TaskTypes.find((taskType) => taskType.type === task.type && taskType.mode === task.mode);
|
||||
|
||||
const { trigger: sendRejection } = useSWRMutation("/api/reject_task", post, {
|
||||
@@ -89,6 +91,7 @@ export const Task = ({ frontendId, task, trigger, mutate }) => {
|
||||
content: replyContent.current,
|
||||
});
|
||||
setTaskStatus("SUBMITTED");
|
||||
scrollToTop(rootEl.current);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -138,7 +141,7 @@ export const Task = ({ frontendId, task, trigger, mutate }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div ref={rootEl}>
|
||||
{taskTypeComponent()}
|
||||
<TaskControls
|
||||
task={task}
|
||||
@@ -164,3 +167,10 @@ export const Task = ({ frontendId, task, trigger, mutate }) => {
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const scrollToTop = (element: HTMLElement) => {
|
||||
while (element) {
|
||||
element.scrollTop = 0;
|
||||
element = element.parentElement;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./Task";
|
||||
@@ -0,0 +1,35 @@
|
||||
import { HStack, IconButton, Link, Stack, Text, useColorModeValue } from "@chakra-ui/react";
|
||||
import { FiHelpCircle } from "react-icons/fi";
|
||||
import type { TaskInfo } from "src/components/Tasks/TaskTypes";
|
||||
|
||||
interface TaskHeaderProps {
|
||||
/**
|
||||
* The `TaskInfo` representing how we present the task to a user.
|
||||
*/
|
||||
taskType: TaskInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the Task label, instructions, and help link
|
||||
*/
|
||||
const TaskHeader = ({ taskType }: TaskHeaderProps) => {
|
||||
const labelColor = useColorModeValue("gray.600", "gray.400");
|
||||
const titleColor = useColorModeValue("gray.800", "gray.300");
|
||||
return (
|
||||
<Stack spacing="1">
|
||||
<HStack>
|
||||
<Text fontSize="xl" fontWeight="bold" color={titleColor}>
|
||||
{taskType.label}
|
||||
</Text>
|
||||
<Link href={taskType.help_link} isExternal>
|
||||
<IconButton variant="ghost" aria-label="More Information" icon={<FiHelpCircle />} />
|
||||
</Link>
|
||||
</HStack>
|
||||
<Text fontSize="md" color={labelColor}>
|
||||
{taskType.overview}
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export { TaskHeader };
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./TaskHeader";
|
||||
@@ -11,6 +11,7 @@ export interface TaskInfo {
|
||||
category: TaskCategory;
|
||||
pathname: string;
|
||||
type: string;
|
||||
help_link: string;
|
||||
mode?: string;
|
||||
overview?: string;
|
||||
instruction?: string;
|
||||
@@ -26,6 +27,7 @@ export const TaskTypes: TaskInfo[] = [
|
||||
desc: "Help us improve Open Assistant by starting a random task.",
|
||||
category: TaskCategory.Tasks,
|
||||
pathname: "/tasks/random",
|
||||
help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
|
||||
type: "random",
|
||||
update_type: "random",
|
||||
},
|
||||
@@ -35,6 +37,7 @@ export const TaskTypes: TaskInfo[] = [
|
||||
desc: "Write initial prompts to help Open Assistant to try replying to diverse messages.",
|
||||
category: TaskCategory.Create,
|
||||
pathname: "/create/initial_prompt",
|
||||
help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
|
||||
type: "initial_prompt",
|
||||
overview: "Create an initial message to send to the assistant",
|
||||
instruction: "Provide the initial prompt",
|
||||
@@ -45,6 +48,7 @@ export const TaskTypes: TaskInfo[] = [
|
||||
desc: "Chat with Open Assistant and help improve it’s responses as you interact with it.",
|
||||
category: TaskCategory.Create,
|
||||
pathname: "/create/user_reply",
|
||||
help_link: "https://projects.laion.ai/Open-Assistant/docs/tasks/reply_as_user",
|
||||
type: "prompter_reply",
|
||||
overview: "Given the following conversation, provide an adequate reply",
|
||||
instruction: "Provide the user's reply",
|
||||
@@ -55,6 +59,7 @@ export const TaskTypes: TaskInfo[] = [
|
||||
desc: "Help Open Assistant improve its responses to conversations with other users.",
|
||||
category: TaskCategory.Create,
|
||||
pathname: "/create/assistant_reply",
|
||||
help_link: "https://projects.laion.ai/Open-Assistant/docs/tasks/reply_as_assistant",
|
||||
type: "assistant_reply",
|
||||
overview: "Given the following conversation, provide an adequate reply",
|
||||
instruction: "Provide the assistant's reply",
|
||||
@@ -66,6 +71,8 @@ export const TaskTypes: TaskInfo[] = [
|
||||
category: TaskCategory.Evaluate,
|
||||
desc: "Help Open Assistant improve its responses to conversations with other users.",
|
||||
pathname: "/evaluate/rank_user_replies",
|
||||
help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
|
||||
overview: "Given the following User replies, sort them from best to worst, best being first, worst being last.",
|
||||
type: "rank_prompter_replies",
|
||||
update_type: "message_ranking",
|
||||
unchanged_title: "Order Unchanged",
|
||||
@@ -76,6 +83,9 @@ export const TaskTypes: TaskInfo[] = [
|
||||
desc: "Score prompts given by Open Assistant based on their accuracy and readability.",
|
||||
category: TaskCategory.Evaluate,
|
||||
pathname: "/evaluate/rank_assistant_replies",
|
||||
help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
|
||||
overview:
|
||||
"Given the following Assistant replies, sort them from best to worst, best being first, worst being last.",
|
||||
type: "rank_assistant_replies",
|
||||
update_type: "message_ranking",
|
||||
unchanged_title: "Order Unchanged",
|
||||
@@ -86,17 +96,20 @@ export const TaskTypes: TaskInfo[] = [
|
||||
desc: "Score prompts given by Open Assistant based on their accuracy and readability.",
|
||||
category: TaskCategory.Evaluate,
|
||||
pathname: "/evaluate/rank_initial_prompts",
|
||||
help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
|
||||
overview: "Given the following inital prompts, sort them from best to worst, best being first, worst being last.",
|
||||
type: "rank_initial_prompts",
|
||||
update_type: "message_ranking",
|
||||
unchanged_title: "Order Unchanged",
|
||||
unchanged_message: "You have not changed the order of the prompts. Are you sure you would like to continue?",
|
||||
},
|
||||
// label (fuill)
|
||||
// label (full)
|
||||
{
|
||||
label: "Label Initial Prompt",
|
||||
desc: "Provide labels for a prompt.",
|
||||
category: TaskCategory.Label,
|
||||
pathname: "/label/label_initial_prompt",
|
||||
help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
|
||||
overview: "Provide labels for the following prompt",
|
||||
type: "label_initial_prompt",
|
||||
mode: "full",
|
||||
@@ -107,7 +120,8 @@ export const TaskTypes: TaskInfo[] = [
|
||||
desc: "Provide labels for a prompt.",
|
||||
category: TaskCategory.Label,
|
||||
pathname: "/label/label_prompter_reply",
|
||||
overview: "Given the following discussion, provide labels for the final promp",
|
||||
help_link: "https://projects.laion.ai/Open-Assistant/docs/tasks/label_prompter_reply",
|
||||
overview: "Given the following discussion, provide labels for the final prompt",
|
||||
type: "label_prompter_reply",
|
||||
mode: "full",
|
||||
update_type: "text_labels",
|
||||
@@ -117,6 +131,7 @@ export const TaskTypes: TaskInfo[] = [
|
||||
desc: "Provide labels for a prompt.",
|
||||
category: TaskCategory.Label,
|
||||
pathname: "/label/label_assistant_reply",
|
||||
help_link: "https://projects.laion.ai/Open-Assistant/docs/tasks/label_assistant_reply",
|
||||
overview: "Given the following discussion, provide labels for the final prompt.",
|
||||
type: "label_assistant_reply",
|
||||
mode: "full",
|
||||
@@ -128,6 +143,7 @@ export const TaskTypes: TaskInfo[] = [
|
||||
desc: "Provide labels for a prompt.",
|
||||
category: TaskCategory.Label,
|
||||
pathname: "/label/label_initial_prompt",
|
||||
help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
|
||||
overview: "Read the following prompt and then answer the question about it.",
|
||||
type: "label_initial_prompt",
|
||||
mode: "simple",
|
||||
@@ -138,7 +154,8 @@ export const TaskTypes: TaskInfo[] = [
|
||||
desc: "Provide labels for a prompt.",
|
||||
category: TaskCategory.Label,
|
||||
pathname: "/label/label_prompter_reply",
|
||||
overview: "Read the following conversation and then answer the question about the last prompt in the disscusion.",
|
||||
help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
|
||||
overview: "Read the following conversation and then answer the question about the last prompt in the discussion.",
|
||||
type: "label_prompter_reply",
|
||||
mode: "simple",
|
||||
update_type: "text_labels",
|
||||
@@ -148,7 +165,8 @@ export const TaskTypes: TaskInfo[] = [
|
||||
desc: "Provide labels for a prompt.",
|
||||
category: TaskCategory.Label,
|
||||
pathname: "/label/label_assistant_reply",
|
||||
overview: "Read the following conversation and then answer the question about the last prompt in the disscusion.",
|
||||
help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
|
||||
overview: "Read the following conversation and then answer the question about the last prompt in the discussion.",
|
||||
type: "label_assistant_reply",
|
||||
mode: "simple",
|
||||
update_type: "text_labels",
|
||||
|
||||
@@ -16,7 +16,13 @@ export class OasstError {
|
||||
}
|
||||
|
||||
export class OasstApiClient {
|
||||
constructor(private readonly oasstApiUrl: string, private readonly oasstApiKey: string) {}
|
||||
oasstApiUrl: string;
|
||||
oasstApiKey: string;
|
||||
|
||||
constructor(oasstApiUrl: string, oasstApiKey: string) {
|
||||
this.oasstApiUrl = oasstApiUrl;
|
||||
this.oasstApiKey = oasstApiKey;
|
||||
}
|
||||
|
||||
private async post(path: string, body: any): Promise<any> {
|
||||
const resp = await fetch(`${this.oasstApiUrl}${path}`, {
|
||||
@@ -170,7 +176,7 @@ export class OasstApiClient {
|
||||
const params = new URLSearchParams();
|
||||
params.append("max_count", max_count.toString());
|
||||
|
||||
// The backend API uses different query paramters depending on the
|
||||
// The backend API uses different query parameters depending on the
|
||||
// pagination direction but they both take the same cursor value.
|
||||
// Depending on direction, pick the right query param.
|
||||
if (cursor !== "") {
|
||||
|
||||
@@ -6,9 +6,6 @@ import { PageEmptyState } from "src/components/EmptyState";
|
||||
import { getTransparentHeaderLayout } from "src/components/Layout";
|
||||
|
||||
function Error() {
|
||||
const router = useRouter();
|
||||
const backgroundColor = useColorModeValue("white", "gray.800");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
|
||||
@@ -2,27 +2,11 @@ import { Button, Input, InputGroup } from "@chakra-ui/react";
|
||||
import Head from "next/head";
|
||||
import Router from "next/router";
|
||||
import { useSession } from "next-auth/react";
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { Control, useForm, useWatch } from "react-hook-form";
|
||||
|
||||
export default function Account() {
|
||||
const { data: session } = useSession();
|
||||
const [username, setUsername] = useState("");
|
||||
|
||||
const updateUser = async (e: React.SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const body = { username };
|
||||
await fetch("/api/username", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
session.user.name = username;
|
||||
await Router.push("/account");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
if (!session) {
|
||||
return;
|
||||
@@ -39,21 +23,52 @@ export default function Account() {
|
||||
<div className="oa-basic-theme">
|
||||
<main className="h-3/4 z-0 flex flex-col items-center justify-center">
|
||||
<p>{session.user.name || "No username"}</p>
|
||||
<form onSubmit={updateUser}>
|
||||
<InputGroup>
|
||||
<Input
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
placeholder="Edit Username"
|
||||
type="text"
|
||||
value={username}
|
||||
></Input>
|
||||
<Button disabled={!username} type="submit" value="Change">
|
||||
Submit
|
||||
</Button>
|
||||
</InputGroup>
|
||||
</form>
|
||||
<EditForm></EditForm>
|
||||
</main>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const EditForm = () => {
|
||||
const { data: session } = useSession();
|
||||
|
||||
const updateUser = async ({ username }: { username: string }) => {
|
||||
try {
|
||||
const body = { username };
|
||||
await fetch("/api/username", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
session.user.name = username;
|
||||
await Router.push("/account");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const { register, handleSubmit, control } = useForm<{ username: string }>({
|
||||
defaultValues: {
|
||||
username: session?.user.name,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(updateUser)}>
|
||||
<InputGroup>
|
||||
<Input placeholder="Edit Username" type="text" {...register("username")}></Input>
|
||||
<SubmitButton control={control}></SubmitButton>
|
||||
</InputGroup>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
const SubmitButton = ({ control }: { control: Control<{ username: string }> }) => {
|
||||
const username = useWatch({ control, name: "username" });
|
||||
return (
|
||||
<Button disabled={!username} type="submit" value="Change">
|
||||
Submit
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ const handler = withoutRole("banned", async (req, res, token) => {
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the task and link it to the user..
|
||||
|
||||
@@ -6,11 +6,12 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { ClientSafeProvider, getProviders, signIn } from "next-auth/react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { FaBug, FaDiscord, FaEnvelope, FaGithub } from "react-icons/fa";
|
||||
import { AuthLayout } from "src/components/AuthLayout";
|
||||
import { Footer } from "src/components/Footer";
|
||||
import { Header } from "src/components/Header";
|
||||
import { RoleSelect } from "src/components/RoleSelect";
|
||||
import { Role, RoleSelect } from "src/components/RoleSelect";
|
||||
|
||||
export type SignInErrorTypes =
|
||||
| "Signin"
|
||||
@@ -42,7 +43,7 @@ const errorMessages: Record<SignInErrorTypes, string> = {
|
||||
interface SigninProps {
|
||||
providers: Awaited<ReturnType<typeof getProviders>>;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
function Signin({ providers }: SigninProps) {
|
||||
const router = useRouter();
|
||||
const { discord, email, github, credentials } = providers;
|
||||
@@ -60,15 +61,14 @@ function Signin({ providers }: SigninProps) {
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
const signinWithEmail = (ev: React.FormEvent) => {
|
||||
ev.preventDefault();
|
||||
signIn(email.id, { callbackUrl: "/dashboard", email: emailEl.current.value });
|
||||
const signinWithEmail = (data: { email: string }) => {
|
||||
signIn(email.id, { callbackUrl: "/dashboard", email: data.email });
|
||||
};
|
||||
|
||||
const { colorMode } = useColorMode();
|
||||
const bgColorClass = colorMode === "light" ? "bg-gray-50" : "bg-chakra-gray-900";
|
||||
const buttonBgColor = colorMode === "light" ? "#2563eb" : "#2563eb";
|
||||
|
||||
const { register, handleSubmit } = useForm<{ email: string }>();
|
||||
return (
|
||||
<div className={bgColorClass}>
|
||||
<Head>
|
||||
@@ -79,7 +79,7 @@ function Signin({ providers }: SigninProps) {
|
||||
<Stack spacing="2">
|
||||
{credentials && <DebugSigninForm credentials={credentials} bgColorClass={bgColorClass} />}
|
||||
{email && (
|
||||
<form onSubmit={signinWithEmail}>
|
||||
<form onSubmit={handleSubmit(signinWithEmail)}>
|
||||
<Stack>
|
||||
<Input
|
||||
type="email"
|
||||
@@ -87,7 +87,7 @@ function Signin({ providers }: SigninProps) {
|
||||
variant="outline"
|
||||
size="lg"
|
||||
placeholder="Email Address"
|
||||
ref={emailEl}
|
||||
{...register("email")}
|
||||
/>
|
||||
<SigninButton data-cy="signin-email-button" leftIcon={<FaEnvelope />}>
|
||||
Continue with Email
|
||||
@@ -174,23 +174,35 @@ const SigninButton = (props: ButtonProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
interface DebugSigninFormData {
|
||||
username: string;
|
||||
role: Role;
|
||||
}
|
||||
|
||||
const DebugSigninForm = ({ credentials, bgColorClass }: { credentials: ClientSafeProvider; bgColorClass: string }) => {
|
||||
const debugUsernameEl = useRef(null);
|
||||
const roleRef = useRef(null);
|
||||
function signinWithDebugCredentials(ev: React.FormEvent) {
|
||||
ev.preventDefault();
|
||||
const { register, handleSubmit } = useForm<DebugSigninFormData>({
|
||||
defaultValues: {
|
||||
role: "general",
|
||||
username: "dev",
|
||||
},
|
||||
});
|
||||
|
||||
function signinWithDebugCredentials(data: DebugSigninFormData) {
|
||||
signIn(credentials.id, {
|
||||
callbackUrl: "/dashboard",
|
||||
username: debugUsernameEl.current.value,
|
||||
role: roleRef.current.value,
|
||||
...data,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={signinWithDebugCredentials} className="border-2 border-orange-600 rounded-md p-4 relative">
|
||||
<form
|
||||
onSubmit={handleSubmit(signinWithDebugCredentials)}
|
||||
className="border-2 border-orange-600 rounded-md p-4 relative"
|
||||
>
|
||||
<span className={`text-orange-600 absolute -top-3 left-5 ${bgColorClass} px-1`}>For Debugging Only</span>
|
||||
<Stack>
|
||||
<Input variant="outline" size="lg" placeholder="Username" ref={debugUsernameEl} />
|
||||
<RoleSelect defaultValue={"general"} ref={roleRef}></RoleSelect>
|
||||
<Input variant="outline" size="lg" placeholder="Username" {...register("username")} />
|
||||
<RoleSelect {...register("role")}></RoleSelect>
|
||||
<SigninButton leftIcon={<FaBug />}>Continue with Debug User</SigninButton>
|
||||
</Stack>
|
||||
</form>
|
||||
|
||||
@@ -19,8 +19,8 @@ const InitialPrompt = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Reply as Assistant</title>
|
||||
<meta name="description" content="Reply as Assistant." />
|
||||
<title>Initial Prompt</title>
|
||||
<meta name="description" content="Add an initial Prompt." />
|
||||
</Head>
|
||||
<Task key={tasks[0].task.id} frontendId={tasks[0].id} task={tasks[0].task} trigger={trigger} mutate={reset} />
|
||||
</>
|
||||
|
||||
@@ -19,8 +19,8 @@ const UserReply = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Reply as Assistant</title>
|
||||
<meta name="description" content="Reply as Assistant." />
|
||||
<title>Reply as User</title>
|
||||
<meta name="description" content="Reply as User." />
|
||||
</Head>
|
||||
<Task key={tasks[0].task.id} frontendId={tasks[0].id} task={tasks[0].task} trigger={trigger} mutate={reset} />
|
||||
</>
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
import { Flex } from "@chakra-ui/react";
|
||||
import Head from "next/head";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { LeaderboardTable, TaskOption } from "src/components/Dashboard";
|
||||
import { LeaderboardTable, TaskOption, WelcomeCard } from "src/components/Dashboard";
|
||||
import { getDashboardLayout } from "src/components/Layout";
|
||||
import { TaskCategory } from "src/components/Tasks/TaskTypes";
|
||||
|
||||
const Dashboard = () => {
|
||||
const { data: session } = useSession();
|
||||
|
||||
// TODO(#670): Do something more meaningful when the user is new.
|
||||
console.log(session?.user?.isNew);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Dashboard - Open Assistant</title>
|
||||
<meta name="description" content="Chat with Open Assistant and provide feedback." />
|
||||
</Head>
|
||||
<TaskOption displayTaskCategories={[TaskCategory.Tasks]} />
|
||||
<LeaderboardTable />
|
||||
<Flex direction="column" gap="10">
|
||||
<WelcomeCard />
|
||||
<TaskOption displayTaskCategories={[TaskCategory.Tasks]} />
|
||||
<LeaderboardTable />
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Box, Heading, Tabs, TabList, TabPanels, Tab, TabPanel } from "@chakra-ui/react";
|
||||
import { Box, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
|
||||
import Head from "next/head";
|
||||
import { getDashboardLayout } from "src/components/Layout";
|
||||
import { LeaderboardGridCell } from "src/components/LeaderboardGridCell";
|
||||
|
||||
@@ -88,7 +88,7 @@ const PrivacyPolicy = () => {
|
||||
{
|
||||
number: "4",
|
||||
title: "Inquiries",
|
||||
desc: "When you contact us via e-mail, telephone or telefax, your inquiry, including all personal data arising thereof will be stored by us for the purpose of processing your request. We will not pass on these data without your consent. The processing of these data is based on Article 6 (1) (1) (b) GDPR, if your inquiry is related to the fulfilment of a contract concluded with us or required for the implementation of pre-contractual measures. Furthermore, the processing is based on Article 6 (1) (1) (f) GDPR, because we have a legitimate interest in the effective handling of requests sent to us. In addition, according to Article 6 (1) (1) (c) GDPR we are also entitled to the processing of the above-mentioned data, because we are legally bound to enable fast electronic contact and immediate communication. Of course, your data will only be used strictly according to purpose and only for processing and responding to your request. After final processing, your data will immediately be anonymized or deleted, unless we are bound by a legally prescribed storage period.",
|
||||
desc: "When you contact us via e-mail, telephone or telefax, your inquiry, including all personal data arising thereof will be stored by us for the purpose of processing your request. We will not pass on these data without your consent. The processing of these data is based on Article 6 (1) (1) (b) GDPR, if your inquiry is related to the fulfillment of a contract concluded with us or required for the implementation of pre-contractual measures. Furthermore, the processing is based on Article 6 (1) (1) (f) GDPR, because we have a legitimate interest in the effective handling of requests sent to us. In addition, according to Article 6 (1) (1) (c) GDPR we are also entitled to the processing of the above-mentioned data, because we are legally bound to enable fast electronic contact and immediate communication. Of course, your data will only be used strictly according to purpose and only for processing and responding to your request. After final processing, your data will immediately be anonymized or deleted, unless we are bound by a legally prescribed storage period.",
|
||||
sections: [],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ const TermsOfService = () => {
|
||||
{
|
||||
number: "1.1",
|
||||
title: "",
|
||||
desc: `LAION (association in formation), Marie-Henning-Weg 143, 21035 Hamburg (hereinafter referred to as: "LAION") operates an online portal for the producing a machine learning model called Open Assistant using crowdsourced data.`,
|
||||
desc: `LAION (association in formation), Marie-Henning-Weg 143, 21035 Hamburg (hereinafter referred to as: "LAION") operates an online portal for the producing a machine learning model called Open Assistant using crowd-sourced data.`,
|
||||
},
|
||||
{
|
||||
number: "1.2",
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
export interface TaskReplyNotSubmittable<T> {
|
||||
content: T;
|
||||
state: "NOT_SUBMITTABLE";
|
||||
}
|
||||
export interface TaskReplyValid<T> {
|
||||
content: T;
|
||||
state: "VALID";
|
||||
@@ -11,4 +15,8 @@ export interface TaskReplyInValid<T> {
|
||||
state: "INVALID";
|
||||
}
|
||||
|
||||
export type TaskReplyState<T> = TaskReplyValid<T> | TaskReplyDefault<T> | TaskReplyInValid<T>;
|
||||
export type TaskReplyState<T> =
|
||||
| TaskReplyNotSubmittable<T>
|
||||
| TaskReplyValid<T>
|
||||
| TaskReplyDefault<T>
|
||||
| TaskReplyInValid<T>;
|
||||
|
||||
Reference in New Issue
Block a user