Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e568cf9d37
|
||
|
|
a25c20e8c8
|
||
|
|
8bd371824d
|
||
|
|
f40406e49b
|
||
|
|
6535efc51f
|
||
|
|
231c4b3991
|
||
|
|
03b3e3e701
|
||
|
|
1f96bf1d16
|
||
|
|
4431b19461
|
||
|
|
01a3ddaefa
|
||
|
|
1a1129f3d1
|
||
|
|
4904160431
|
||
|
|
c7fc3308b0
|
@@ -1274,7 +1274,9 @@ stored as that lossless master. MP3 (320 kbps CBR, LAME) is a download
|
||||
format only: `/content?format=mp3` transcodes the FLAC once and caches
|
||||
`{id}.mp3` beside it until the job expires; `response_format` sets a
|
||||
job's default download. Results stored as WAV by older neurons are
|
||||
served as WAV.
|
||||
served as WAV. cortex refuses new jobs (503 `audio_storage_full`) while the job
|
||||
directory's filesystem has under `[audio_jobs] min_free_mb` free (10 GiB
|
||||
default); shared object storage for the Pascal-scale fleet is #411.
|
||||
|
||||
**Metering: per audio-second**, at a per-model rate stored in
|
||||
helexa-upstream's `model_rates` table (`GET /authz/v1/rates`, cached by
|
||||
|
||||
Generated
+1
@@ -875,6 +875,7 @@ dependencies = [
|
||||
"metrics-exporter-prometheus",
|
||||
"mp3lame-encoder",
|
||||
"reqwest",
|
||||
"rustix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
|
||||
@@ -129,3 +129,6 @@ enabled = false
|
||||
# max_attempts = 2
|
||||
# How long a job may wait on a busy model before failing, in seconds.
|
||||
# queue_timeout_secs = 3600
|
||||
# New jobs are refused (503) while the job directory's filesystem has less
|
||||
# than this much free space, in MiB. 0 disables the check.
|
||||
# min_free_mb = 10240
|
||||
|
||||
@@ -57,6 +57,12 @@ pub struct AudioJobsConfig {
|
||||
/// How long a job may wait for a busy model before it fails, in seconds.
|
||||
#[serde(default = "default_audio_queue_timeout")]
|
||||
pub queue_timeout_secs: u64,
|
||||
/// New jobs are refused (503 + `Retry-After`) while the filesystem
|
||||
/// holding `dir` has less than this much free space, in MiB. Rendered
|
||||
/// audio is written there, so this keeps a busy store from filling the
|
||||
/// disk it shares. 0 disables the check.
|
||||
#[serde(default = "default_audio_min_free_mb")]
|
||||
pub min_free_mb: u64,
|
||||
}
|
||||
|
||||
impl Default for AudioJobsConfig {
|
||||
@@ -67,6 +73,7 @@ impl Default for AudioJobsConfig {
|
||||
max_concurrent: default_audio_max_concurrent(),
|
||||
max_attempts: default_audio_max_attempts(),
|
||||
queue_timeout_secs: default_audio_queue_timeout(),
|
||||
min_free_mb: default_audio_min_free_mb(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,6 +93,11 @@ fn default_audio_max_attempts() -> u32 {
|
||||
fn default_audio_queue_timeout() -> u64 {
|
||||
3600
|
||||
}
|
||||
/// 10 GiB: room for well over a hundred of the longest songs while jobs
|
||||
/// already accepted finish.
|
||||
fn default_audio_min_free_mb() -> u64 {
|
||||
10 * 1024
|
||||
}
|
||||
|
||||
/// `[upstream]` — the helexa-upstream authority client (#57). Locally
|
||||
/// unrecognised bearer keys are resolved against `url`'s `/authz/v1` surface
|
||||
|
||||
@@ -28,6 +28,7 @@ bytes = "1"
|
||||
urlencoding = "2"
|
||||
url = "2"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
rustix = { version = "1", default-features = false, features = ["std", "fs"] }
|
||||
claxon = "0.4.3"
|
||||
mp3lame-encoder = { version = "0.2.5", features = ["std"] }
|
||||
|
||||
|
||||
@@ -55,6 +55,10 @@ const DEFAULT_BUSY_WAIT: Duration = Duration::from_secs(5);
|
||||
/// How often expired jobs are swept.
|
||||
const SWEEP_INTERVAL: Duration = Duration::from_secs(600);
|
||||
|
||||
/// `Retry-After` on a submission refused for lack of disk space: about as
|
||||
/// long as it takes the sweep to come round and expire old jobs.
|
||||
const STORAGE_RETRY_AFTER_SECS: u64 = 600;
|
||||
|
||||
/// A job as cortex stores it: the public object plus what is needed to run
|
||||
/// (or re-run) it.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -126,6 +130,30 @@ impl AudioJobs {
|
||||
.as_ref()
|
||||
}
|
||||
|
||||
/// Free space on the filesystem holding the job directory, in MiB.
|
||||
/// `None` when there is no directory (memory only) or it cannot be
|
||||
/// read, in which case submissions are not refused on its account.
|
||||
fn free_mb(&self) -> Option<u64> {
|
||||
let dir = self.dir()?;
|
||||
match rustix::fs::statvfs(dir) {
|
||||
Ok(st) => Some(st.f_bavail.saturating_mul(st.f_frsize) / (1024 * 1024)),
|
||||
Err(e) => {
|
||||
tracing::warn!(dir = %dir.display(), error = %e, "cannot read free space for audio jobs");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a new job may be accepted: the store's filesystem must keep
|
||||
/// at least `min_free_mb` free. Returns the free space when it may not.
|
||||
fn storage_full(&self) -> Option<u64> {
|
||||
let floor = self.config.min_free_mb;
|
||||
if floor == 0 {
|
||||
return None;
|
||||
}
|
||||
self.free_mb().filter(|free| *free < floor)
|
||||
}
|
||||
|
||||
fn record_path(&self, id: &str) -> Option<PathBuf> {
|
||||
self.dir().map(|d| d.join(format!("{id}.json")))
|
||||
}
|
||||
@@ -342,6 +370,25 @@ async fn submit(
|
||||
return invalid_params(msg);
|
||||
}
|
||||
|
||||
// Refused before any budget is reserved: nothing to release.
|
||||
if let Some(free) = fleet.audio_jobs.storage_full() {
|
||||
metrics::counter!("cortex_audio_jobs_rejected_total", "reason" => "storage").increment(1);
|
||||
tracing::warn!(
|
||||
free_mb = free,
|
||||
min_free_mb = fleet.audio_jobs.config.min_free_mb,
|
||||
"audio job refused: job store is low on disk space"
|
||||
);
|
||||
return crate::error::envelope_response(
|
||||
OpenAiError::new(
|
||||
503,
|
||||
"server_error",
|
||||
"audio_storage_full",
|
||||
"audio generation is temporarily unavailable: the job store is full",
|
||||
)
|
||||
.with_retry_after(STORAGE_RETRY_AFTER_SECS),
|
||||
);
|
||||
}
|
||||
|
||||
let owner = crate::metering::principal_from_headers(&headers);
|
||||
let mut request = request;
|
||||
request.model = model.clone();
|
||||
|
||||
@@ -46,6 +46,7 @@ pub async fn evict_lru_on_node(
|
||||
.models
|
||||
.values()
|
||||
.filter(|m| m.status == ModelStatus::Loaded)
|
||||
.filter(|m| !fleet.eviction_blocked(node, &m.id))
|
||||
.filter(|m| match incoming {
|
||||
Some(inc) => fleet.catalogue.may_displace(inc, &m.id),
|
||||
None => {
|
||||
|
||||
@@ -310,7 +310,7 @@ async fn discover_capabilities(fleet: &CortexState, name: &str, endpoint: &str)
|
||||
/// Fetch `/health` and stash the activation snapshot on NodeState.
|
||||
/// Decoupled from the /models poll so a /health glitch doesn't mark
|
||||
/// the neuron unhealthy or evict the model list.
|
||||
async fn poll_health(fleet: &CortexState, name: &str, endpoint: &str) {
|
||||
pub async fn poll_health(fleet: &CortexState, name: &str, endpoint: &str) {
|
||||
let url = format!("{endpoint}/health");
|
||||
let resp = match fleet
|
||||
.http_client
|
||||
|
||||
@@ -64,6 +64,10 @@ pub enum RouteError {
|
||||
"model '{model_id}' is recovering on node '{node}' (device context rebuild in progress) — retry shortly"
|
||||
)]
|
||||
ModelRecovering { model_id: String, node: String },
|
||||
#[error(
|
||||
"model '{model_id}' does not fit on '{node}' until a model there finishes its work — retry shortly"
|
||||
)]
|
||||
NodeBusy { model_id: String, node: String },
|
||||
}
|
||||
|
||||
impl RouteError {
|
||||
@@ -74,7 +78,8 @@ impl RouteError {
|
||||
match self {
|
||||
RouteError::NoHealthyNodes
|
||||
| RouteError::ModelRecovering { .. }
|
||||
| RouteError::FeasibleNodeUnhealthy { .. } => 503,
|
||||
| RouteError::FeasibleNodeUnhealthy { .. }
|
||||
| RouteError::NodeBusy { .. } => 503,
|
||||
_ => 404,
|
||||
}
|
||||
}
|
||||
@@ -88,7 +93,8 @@ impl RouteError {
|
||||
| RouteError::NoFeasibleNeuron { .. }
|
||||
| RouteError::ColdLoadFailed { .. }
|
||||
| RouteError::ModelRecovering { .. }
|
||||
| RouteError::FeasibleNodeUnhealthy { .. } => "api_error",
|
||||
| RouteError::FeasibleNodeUnhealthy { .. }
|
||||
| RouteError::NodeBusy { .. } => "api_error",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +108,7 @@ impl RouteError {
|
||||
RouteError::ColdLoadFailed { .. } => "service_unavailable",
|
||||
RouteError::ModelRecovering { .. } => "service_unavailable",
|
||||
RouteError::FeasibleNodeUnhealthy { .. } => "service_unavailable",
|
||||
RouteError::NodeBusy { .. } => "service_unavailable",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,6 +122,8 @@ impl RouteError {
|
||||
RouteError::ModelRecovering { .. } => Some(2),
|
||||
RouteError::FeasibleNodeUnhealthy { .. } => Some(3),
|
||||
RouteError::NoHealthyNodes => Some(5),
|
||||
// Clears when the resident model's requests finish.
|
||||
RouteError::NodeBusy { .. } => Some(10),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -205,11 +214,10 @@ pub async fn resolve(
|
||||
// Loading is gateway-synthesised from neuron's
|
||||
// activation snapshot; it never appears on the
|
||||
// wire from neuron's `/models`. Skip — the model
|
||||
// isn't actually servable yet. The pre-existing
|
||||
// race (catalogue cold_load fires a parallel
|
||||
// /models/load against the in-flight load) is no
|
||||
// worse than before; fixing it needs neuron-side
|
||||
// in-flight tracking on /models/load itself.
|
||||
// isn't actually servable yet. Cortex's own
|
||||
// cold-loads are serialised per node (priority 4);
|
||||
// a load neuron started by itself at activation is
|
||||
// still invisible to that lock.
|
||||
ModelStatus::Loading => {}
|
||||
}
|
||||
}
|
||||
@@ -247,41 +255,109 @@ pub async fn resolve(
|
||||
|
||||
// Priority 4: catalogue × topology cold-load.
|
||||
if let Some(profile) = fleet.catalogue.get(model_id) {
|
||||
let (node_name, neuron_endpoint, fits_free) = pick_feasible_neuron(fleet, profile).await?;
|
||||
// Cold-swap (#203): when the chosen node's devices don't have the
|
||||
// free VRAM *right now*, evict unpinned LRU models there until
|
||||
// the profile fits (bounded — a node whose evictable set can't
|
||||
// free enough was ranked below one that can).
|
||||
if !fits_free {
|
||||
for _ in 0..3 {
|
||||
match crate::evictor::evict_lru_on_node(fleet, &node_name, Some(&profile.id)).await
|
||||
{
|
||||
Ok(Some(evicted)) => {
|
||||
tracing::info!(
|
||||
model = %profile.id,
|
||||
node = %node_name,
|
||||
evicted = %evicted,
|
||||
"cold-swap: evicted LRU model to make room"
|
||||
);
|
||||
if node_fits_free(fleet, &node_name, profile).await {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(None) => break,
|
||||
Err(e) => {
|
||||
tracing::warn!(node = %node_name, error = %e, "cold-swap eviction failed");
|
||||
break;
|
||||
}
|
||||
let (node_name, neuron_endpoint, _) = pick_feasible_neuron(fleet, profile).await?;
|
||||
// One cold-load per node at a time. The choice above rests on the
|
||||
// last poll's VRAM, which cannot see a load in progress, so every
|
||||
// decision that follows is re-made inside the lock from a fresh
|
||||
// `/health`.
|
||||
let lock = fleet.cold_load_lock(&node_name);
|
||||
let _turn = lock.lock().await;
|
||||
|
||||
// Whoever held the lock may have been loading this very model.
|
||||
if let Some((node, endpoint)) = loaded_location(fleet, model_id).await {
|
||||
return finish(fleet, &node, &endpoint, model_id, false).await;
|
||||
}
|
||||
|
||||
crate::poller::poll_health(fleet, &node_name, &neuron_endpoint).await;
|
||||
// Cold-swap (#203): when the node's devices don't have the free
|
||||
// VRAM right now, evict unpinned LRU models there until the
|
||||
// profile fits (bounded — a node whose evictable set can't free
|
||||
// enough was ranked below one that can).
|
||||
for _ in 0..3 {
|
||||
if node_fits_free(fleet, &node_name, profile).await {
|
||||
break;
|
||||
}
|
||||
match crate::evictor::evict_lru_on_node(fleet, &node_name, Some(&profile.id)).await {
|
||||
Ok(Some(evicted)) => {
|
||||
tracing::info!(
|
||||
model = %profile.id,
|
||||
node = %node_name,
|
||||
evicted = %evicted,
|
||||
"cold-swap: evicted LRU model to make room"
|
||||
);
|
||||
crate::poller::poll_health(fleet, &node_name, &neuron_endpoint).await;
|
||||
}
|
||||
Ok(None) => break,
|
||||
Err(e) => {
|
||||
tracing::warn!(node = %node_name, error = %e, "cold-swap eviction failed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Still short, and a model that would otherwise be displaced is
|
||||
// busy: loading now would land beside it and fail out of memory.
|
||||
// Answer "retry shortly" instead; the room appears when its work
|
||||
// ends.
|
||||
if !node_fits_free(fleet, &node_name, profile).await
|
||||
&& blocked_displaceable(fleet, &node_name, profile).await
|
||||
{
|
||||
tracing::info!(
|
||||
model = %profile.id,
|
||||
node = %node_name,
|
||||
"cold-load deferred: the model it would displace is busy"
|
||||
);
|
||||
return Err(RouteError::NodeBusy {
|
||||
model_id: profile.id.clone(),
|
||||
node: node_name,
|
||||
});
|
||||
}
|
||||
cold_load(fleet, &node_name, &neuron_endpoint, profile).await?;
|
||||
fleet.note_cold_load(&node_name, &profile.id);
|
||||
// The next cold-load in line decides from these readings.
|
||||
crate::poller::poll_health(fleet, &node_name, &neuron_endpoint).await;
|
||||
drop(_turn);
|
||||
return finish(fleet, &node_name, &neuron_endpoint, model_id, true).await;
|
||||
}
|
||||
|
||||
Err(RouteError::ModelNotFound(model_id.to_string()))
|
||||
}
|
||||
|
||||
/// A healthy node where `model_id` is loaded and servable, if any.
|
||||
async fn loaded_location(fleet: &Arc<CortexState>, model_id: &str) -> Option<(String, String)> {
|
||||
let nodes = fleet.nodes.read().await;
|
||||
nodes
|
||||
.values()
|
||||
.filter(|n| n.healthy)
|
||||
.find(|n| {
|
||||
n.models.get(model_id).is_some_and(|m| {
|
||||
matches!(m.status, ModelStatus::Loaded | ModelStatus::Reloading) && m.is_servable()
|
||||
})
|
||||
})
|
||||
.map(|n| (n.name.clone(), n.endpoint.clone()))
|
||||
}
|
||||
|
||||
/// Whether `node_name` holds a model the profile may displace but that
|
||||
/// cannot be evicted right now because it is busy (see
|
||||
/// [`CortexState::eviction_blocked`]), or is still loading its startup
|
||||
/// models.
|
||||
async fn blocked_displaceable(
|
||||
fleet: &Arc<CortexState>,
|
||||
node_name: &str,
|
||||
profile: &ModelProfile,
|
||||
) -> bool {
|
||||
let nodes = fleet.nodes.read().await;
|
||||
nodes.get(node_name).is_some_and(|node| {
|
||||
// A startup load cannot be evicted until it finishes, and its
|
||||
// memory is still arriving: wait for it.
|
||||
activating(node)
|
||||
|| node.models.values().any(|m| {
|
||||
m.status == ModelStatus::Loaded
|
||||
&& fleet.catalogue.may_displace(&profile.id, &m.id)
|
||||
&& fleet.eviction_blocked(node, &m.id)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// True when `node_name`'s live device readings show enough free VRAM
|
||||
/// on some device for the profile. Conservative: unknown health (no
|
||||
/// /health poll yet) counts as not-fitting so the ranking prefers
|
||||
@@ -289,12 +365,40 @@ pub async fn resolve(
|
||||
async fn node_fits_free(fleet: &Arc<CortexState>, node_name: &str, profile: &ModelProfile) -> bool {
|
||||
let nodes = fleet.nodes.read().await;
|
||||
nodes.get(node_name).is_some_and(|node| {
|
||||
let reserved = activation_reserved_mb(fleet, node);
|
||||
node.device_health
|
||||
.iter()
|
||||
.any(|d| d.vram_free_mb >= profile.vram_mb.unwrap_or(0))
|
||||
.any(|d| d.vram_free_mb.saturating_sub(reserved) >= profile.vram_mb.unwrap_or(0))
|
||||
})
|
||||
}
|
||||
|
||||
/// VRAM promised to models the neuron is loading on its own at startup
|
||||
/// (its `default_models`), by their catalogue footprint. Those loads are
|
||||
/// not in the neuron's model list until they finish and their memory
|
||||
/// fills in gradually, so free VRAM alone overstates what is available.
|
||||
fn activation_reserved_mb(fleet: &CortexState, node: &cortex_core::node::NodeState) -> u64 {
|
||||
node.activation.as_ref().map_or(0, |a| {
|
||||
a.in_progress
|
||||
.iter()
|
||||
.chain(a.pending.iter())
|
||||
.filter(|id| {
|
||||
!node
|
||||
.models
|
||||
.get(id.as_str())
|
||||
.is_some_and(|m| m.status == ModelStatus::Loaded)
|
||||
})
|
||||
.filter_map(|id| fleet.catalogue.get(id).and_then(|p| p.vram_mb))
|
||||
.sum()
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether the neuron is still loading its startup models.
|
||||
fn activating(node: &cortex_core::node::NodeState) -> bool {
|
||||
node.activation
|
||||
.as_ref()
|
||||
.is_some_and(|a| a.in_progress.is_some() || !a.pending.is_empty())
|
||||
}
|
||||
|
||||
/// Pick a healthy neuron whose discovered topology satisfies the
|
||||
/// profile. Preference order (#203):
|
||||
/// 1. A neuron from `profile.pinned_on` that is healthy + feasible.
|
||||
@@ -332,7 +436,8 @@ async fn pick_feasible_neuron(
|
||||
.iter()
|
||||
.map(|d| d.vram_free_mb)
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
.unwrap_or(0)
|
||||
.saturating_sub(activation_reserved_mb(fleet, node));
|
||||
let need = profile.vram_mb.unwrap_or(0);
|
||||
let fits_free = max_free >= need;
|
||||
// Evictable estimate: free + the VRAM of loaded models this
|
||||
@@ -344,6 +449,7 @@ async fn pick_feasible_neuron(
|
||||
.values()
|
||||
.filter(|m| {
|
||||
matches!(m.status, cortex_core::node::ModelStatus::Loaded)
|
||||
&& !fleet.eviction_blocked(node, &m.id)
|
||||
&& fleet.catalogue.may_displace(&profile.id, &m.id)
|
||||
})
|
||||
// neuron reports vram_used_mb: null today; fall back to the
|
||||
|
||||
@@ -57,8 +57,22 @@ pub struct CortexState {
|
||||
/// Per-model rates for non-token units (#386), from upstream when it
|
||||
/// is enabled, else the local default.
|
||||
pub rates: Arc<crate::rates::Rates>,
|
||||
/// One lock per node, held across a cold-load's evict → load → fresh
|
||||
/// `/health` sequence. Placement reads VRAM from the last poll, which
|
||||
/// cannot see a load still in progress; two cold-loads deciding at
|
||||
/// once both saw the same free card and loaded side by side into an
|
||||
/// out-of-memory.
|
||||
cold_load_locks: std::sync::Mutex<HashMap<String, Arc<tokio::sync::Mutex<()>>>>,
|
||||
/// When cortex last cold-loaded each `(node, model)`. A model loaded
|
||||
/// for a request is protected from eviction for [`COLD_LOAD_GRACE`],
|
||||
/// long enough for that request to reach it; otherwise the next
|
||||
/// cold-load in line evicts it before it has served anything.
|
||||
recent_cold_loads: std::sync::Mutex<HashMap<(String, String), std::time::Instant>>,
|
||||
}
|
||||
|
||||
/// How long a freshly cold-loaded model is protected from eviction.
|
||||
pub const COLD_LOAD_GRACE: std::time::Duration = std::time::Duration::from_secs(30);
|
||||
|
||||
impl CortexState {
|
||||
pub fn from_config(config: &GatewayConfig) -> Self {
|
||||
let mut nodes = HashMap::new();
|
||||
@@ -142,6 +156,53 @@ impl CortexState {
|
||||
} else {
|
||||
crate::rates::Rates::local()
|
||||
}),
|
||||
cold_load_locks: std::sync::Mutex::new(HashMap::new()),
|
||||
recent_cold_loads: std::sync::Mutex::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// The lock that serialises cold-loads on `node`.
|
||||
pub fn cold_load_lock(&self, node: &str) -> Arc<tokio::sync::Mutex<()>> {
|
||||
let mut locks = self
|
||||
.cold_load_locks
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
Arc::clone(locks.entry(node.to_string()).or_default())
|
||||
}
|
||||
|
||||
/// Record that cortex has just cold-loaded `model` on `node`.
|
||||
pub fn note_cold_load(&self, node: &str, model: &str) {
|
||||
let mut recent = self
|
||||
.recent_cold_loads
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
recent.retain(|_, at| at.elapsed() < COLD_LOAD_GRACE);
|
||||
recent.insert(
|
||||
(node.to_string(), model.to_string()),
|
||||
std::time::Instant::now(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Whether `model` on `node` is inside its post-cold-load grace.
|
||||
pub fn recently_cold_loaded(&self, node: &str, model: &str) -> bool {
|
||||
let recent = self
|
||||
.recent_cold_loads
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner());
|
||||
recent
|
||||
.get(&(node.to_string(), model.to_string()))
|
||||
.is_some_and(|at| at.elapsed() < COLD_LOAD_GRACE)
|
||||
}
|
||||
|
||||
/// Whether a resident model must not be evicted right now: it has
|
||||
/// requests running or queued (as of the last `/health`), or cortex
|
||||
/// cold-loaded it moments ago for a request that has yet to arrive.
|
||||
/// Evicting either kills work in progress.
|
||||
pub fn eviction_blocked(&self, node: &cortex_core::node::NodeState, model: &str) -> bool {
|
||||
let busy = node
|
||||
.model_load
|
||||
.get(model)
|
||||
.is_some_and(|l| l.in_flight + l.queue_depth > 0);
|
||||
busy || self.recently_cold_loaded(&node.name, model)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +202,9 @@ fn config(neuron: &str, dir: &std::path::Path) -> GatewayConfig {
|
||||
audio_jobs: AudioJobsConfig {
|
||||
dir: dir.to_string_lossy().into_owned(),
|
||||
queue_timeout_secs: 20,
|
||||
// The free-space floor is tested on its own below; elsewhere it
|
||||
// must not make results depend on the build host's disk.
|
||||
min_free_mb: 0,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
@@ -741,6 +744,46 @@ async fn expired_jobs_are_swept() {
|
||||
assert!(!dir.path().join(format!("{id}.mp3")).exists());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn submissions_are_refused_when_the_store_is_low_on_disk() {
|
||||
let mock = spawn_mock(Mode::Ok).await;
|
||||
let rates = [("*", 1000)];
|
||||
|
||||
// A floor no filesystem can meet: refused, retryable, nothing written,
|
||||
// nothing reserved.
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let mut cfg = config(&mock.url, dir.path());
|
||||
cfg.audio_jobs.min_free_mb = u64::MAX;
|
||||
for k in &mut cfg.entitlements.keys {
|
||||
k.hard_cap = Some(1_000_000);
|
||||
}
|
||||
let fleet = Arc::new(CortexState::from_config(&cfg));
|
||||
let mut table = cortex_gateway::rates::RateTable::default();
|
||||
for (model, rate) in rates {
|
||||
table.set(cortex_gateway::rates::AUDIO_SECOND, model, rate);
|
||||
}
|
||||
fleet.rates.replace(table).await;
|
||||
seed_model(&fleet).await;
|
||||
let gw = serve(&fleet).await;
|
||||
|
||||
let resp = submit(&gw, Some("key-alice"), song()).await;
|
||||
assert_eq!(resp.status(), 503);
|
||||
assert_eq!(resp.headers()["retry-after"], "600");
|
||||
let body: Value = resp.json().await.unwrap();
|
||||
assert_eq!(body["error"]["code"], "audio_storage_full");
|
||||
assert_eq!(spent(&fleet, "alice").await, 0);
|
||||
assert_eq!(std::fs::read_dir(dir.path()).unwrap().count(), 0);
|
||||
|
||||
// A floor any filesystem meets: accepted.
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let mut cfg = config(&mock.url, dir.path());
|
||||
cfg.audio_jobs.min_free_mb = 1;
|
||||
let fleet = Arc::new(CortexState::from_config(&cfg));
|
||||
seed_model(&fleet).await;
|
||||
let gw = serve(&fleet).await;
|
||||
assert_eq!(submit(&gw, None, song()).await.status(), 202);
|
||||
}
|
||||
|
||||
// ── Per-model rates (#386) ───────────────────────────────────────────
|
||||
|
||||
/// A gateway whose keys are capped at `cap` tokens and whose audio rate
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
//! Cold-load placement under concurrency.
|
||||
//!
|
||||
//! The mock neuron keeps real VRAM books for one 24 GB card: loads take
|
||||
//! time, `/health` reports what is actually resident, and the card notes
|
||||
//! if it is ever asked to hold more than it has. That last flag is the
|
||||
//! failure being guarded against — two cold-loads deciding from the same
|
||||
//! stale poll, both landing, and every request on the card then failing
|
||||
//! out of memory.
|
||||
|
||||
use axum::extract::{Path, State};
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Json, Router};
|
||||
use cortex_core::config::{
|
||||
EvictionSettings, EvictionStrategy, GatewayConfig, GatewaySettings, NeuronEndpoint,
|
||||
};
|
||||
use cortex_core::discovery::{DeviceInfo, DiscoveryResponse};
|
||||
use cortex_core::node::{ModelEntry, ModelStatus};
|
||||
use cortex_gateway::router::RouteError;
|
||||
use cortex_gateway::state::CortexState;
|
||||
use serde_json::{Value, json};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
const CARD_MB: u64 = 24_000;
|
||||
|
||||
const CATALOGUE: &str = r#"
|
||||
[[models]]
|
||||
id = "music-a"
|
||||
harness = "candle"
|
||||
vram_mb = 14000
|
||||
|
||||
[[models]]
|
||||
id = "music-b"
|
||||
harness = "candle"
|
||||
vram_mb = 12000
|
||||
"#;
|
||||
|
||||
#[derive(Default)]
|
||||
struct Card {
|
||||
resident: HashMap<String, u64>,
|
||||
in_flight: HashMap<String, usize>,
|
||||
overcommitted: bool,
|
||||
unloads: Vec<String>,
|
||||
/// A model the neuron is loading by itself at startup: not resident
|
||||
/// yet, its memory not yet showing.
|
||||
activating: Option<String>,
|
||||
loads: Vec<String>,
|
||||
}
|
||||
|
||||
fn footprint(model: &str) -> u64 {
|
||||
match model {
|
||||
"music-a" => 14_000,
|
||||
"music-b" => 12_000,
|
||||
_ => 1_000,
|
||||
}
|
||||
}
|
||||
|
||||
async fn spawn_neuron(card: Arc<Mutex<Card>>) -> String {
|
||||
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let url = format!("http://{}", listener.local_addr().unwrap());
|
||||
let inference = url.clone();
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/models/load",
|
||||
post(
|
||||
|State(card): State<Arc<Mutex<Card>>>, Json(spec): Json<Value>| async move {
|
||||
let id = spec["model_id"].as_str().unwrap().to_string();
|
||||
// A load takes a while: long enough for a second
|
||||
// request to decide while this one is in progress.
|
||||
tokio::time::sleep(Duration::from_millis(300)).await;
|
||||
let mut card = card.lock().unwrap();
|
||||
card.loads.push(id.clone());
|
||||
card.resident.insert(id.clone(), footprint(&id));
|
||||
if card.resident.values().sum::<u64>() > CARD_MB {
|
||||
card.overcommitted = true;
|
||||
}
|
||||
Json(json!({"status": "loaded"}))
|
||||
},
|
||||
),
|
||||
)
|
||||
.route(
|
||||
"/models/unload",
|
||||
post(
|
||||
|State(card): State<Arc<Mutex<Card>>>, Json(body): Json<Value>| async move {
|
||||
let id = body["model_id"].as_str().unwrap().to_string();
|
||||
let mut card = card.lock().unwrap();
|
||||
card.resident.remove(&id);
|
||||
card.unloads.push(id);
|
||||
Json(json!({"status": "unloaded"}))
|
||||
},
|
||||
),
|
||||
)
|
||||
.route(
|
||||
"/health",
|
||||
get(|State(card): State<Arc<Mutex<Card>>>| async move {
|
||||
let card = card.lock().unwrap();
|
||||
let used: u64 = card.resident.values().sum();
|
||||
let models: Vec<Value> = card
|
||||
.resident
|
||||
.keys()
|
||||
.map(|id| {
|
||||
json!({
|
||||
"id": id,
|
||||
"in_flight": card.in_flight.get(id).copied().unwrap_or(0),
|
||||
"queue_depth": 0
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let activation = match &card.activating {
|
||||
Some(id) => json!({"state": "pre_warming", "in_progress": id}),
|
||||
None => json!({"state": "ready"}),
|
||||
};
|
||||
Json(json!({
|
||||
"uptime_secs": 1,
|
||||
"activation": activation,
|
||||
"devices": [{
|
||||
"index": 0,
|
||||
"vram_used_mb": used,
|
||||
"vram_free_mb": CARD_MB.saturating_sub(used),
|
||||
"utilization_pct": 0,
|
||||
"temp_c": 40
|
||||
}],
|
||||
"models": models
|
||||
}))
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/models/{id}/endpoint",
|
||||
get(move |Path(_id): Path<String>| {
|
||||
let url = inference.clone();
|
||||
async move { Json(json!({"url": url})) }
|
||||
}),
|
||||
)
|
||||
.with_state(card);
|
||||
tokio::spawn(async move { axum::serve(listener, app).await.unwrap() });
|
||||
url
|
||||
}
|
||||
|
||||
/// A gateway with one healthy 24 GB node, its first `/health` already in.
|
||||
async fn gateway(neuron: &str, tag: &str) -> Arc<CortexState> {
|
||||
let path = std::env::temp_dir().join(format!("cortex-cold-load-{tag}.toml"));
|
||||
std::fs::write(&path, CATALOGUE).unwrap();
|
||||
let config = GatewayConfig {
|
||||
gateway: GatewaySettings {
|
||||
listen: "127.0.0.1:0".into(),
|
||||
metrics_listen: "127.0.0.1:0".into(),
|
||||
},
|
||||
eviction: EvictionSettings {
|
||||
strategy: EvictionStrategy::Lru,
|
||||
defrag_after_cycles: 0,
|
||||
},
|
||||
neurons: vec![NeuronEndpoint {
|
||||
name: "gpu".into(),
|
||||
endpoint: neuron.to_string(),
|
||||
}],
|
||||
models_config: path.to_string_lossy().into_owned(),
|
||||
entitlements: Default::default(),
|
||||
upstream: Default::default(),
|
||||
audio_jobs: Default::default(),
|
||||
};
|
||||
let fleet = Arc::new(CortexState::from_config(&config));
|
||||
{
|
||||
let mut nodes = fleet.nodes.write().await;
|
||||
let node = nodes.get_mut("gpu").unwrap();
|
||||
node.healthy = true;
|
||||
node.discovery = Some(DiscoveryResponse {
|
||||
hostname: "gpu".into(),
|
||||
os: "linux".into(),
|
||||
kernel: "6".into(),
|
||||
cuda_version: None,
|
||||
driver_version: None,
|
||||
devices: vec![DeviceInfo {
|
||||
index: 0,
|
||||
name: "RTX 4090".into(),
|
||||
vram_total_mb: CARD_MB,
|
||||
compute_capability: "8.9".into(),
|
||||
}],
|
||||
harnesses: vec!["candle".into()],
|
||||
cuda_unavailable_reason: None,
|
||||
max_prompt_tokens: 0,
|
||||
});
|
||||
}
|
||||
cortex_gateway::poller::poll_health(&fleet, "gpu", neuron).await;
|
||||
fleet
|
||||
}
|
||||
|
||||
/// Mark `id` resident in the mock and in cortex's view, last used long
|
||||
/// ago — as the poller would have seen it, not as a fresh cold-load.
|
||||
async fn seed_resident(fleet: &CortexState, card: &Mutex<Card>, neuron: &str, id: &str) {
|
||||
card.lock()
|
||||
.unwrap()
|
||||
.resident
|
||||
.insert(id.into(), footprint(id));
|
||||
{
|
||||
let mut nodes = fleet.nodes.write().await;
|
||||
nodes.get_mut("gpu").unwrap().models.insert(
|
||||
id.into(),
|
||||
ModelEntry {
|
||||
id: id.into(),
|
||||
status: ModelStatus::Loaded,
|
||||
last_accessed: Some(chrono::Utc::now() - chrono::Duration::minutes(10)),
|
||||
vram_estimate_mb: Some(footprint(id)),
|
||||
capabilities: Vec::new(),
|
||||
tool_call: false,
|
||||
reasoning: false,
|
||||
limit: None,
|
||||
servable: None,
|
||||
reasoning_budget: Vec::new(),
|
||||
},
|
||||
);
|
||||
}
|
||||
cortex_gateway::poller::poll_health(fleet, "gpu", neuron).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn concurrent_cold_loads_never_overcommit_the_card() {
|
||||
let card = Arc::new(Mutex::new(Card::default()));
|
||||
let neuron = spawn_neuron(Arc::clone(&card)).await;
|
||||
let fleet = gateway(&neuron, "concurrent").await;
|
||||
|
||||
// Both decide while the card is empty; together they do not fit.
|
||||
let (a, b) = tokio::join!(cortex_gateway::router::resolve(&fleet, "music-a"), async {
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
cortex_gateway::router::resolve(&fleet, "music-b").await
|
||||
});
|
||||
|
||||
let card = card.lock().unwrap();
|
||||
assert!(!card.overcommitted, "both models were resident at once");
|
||||
assert!(a.is_ok(), "{a:?}");
|
||||
// music-a was cold-loaded for a request that has yet to reach it, so
|
||||
// music-b may not take its place yet: retry shortly, not a failure.
|
||||
match b {
|
||||
Err(e @ RouteError::NodeBusy { .. }) => {
|
||||
assert_eq!(e.http_status(), 503);
|
||||
assert!(e.retry_after_secs().is_some());
|
||||
}
|
||||
other => panic!("expected NodeBusy, got {other:?}"),
|
||||
}
|
||||
assert_eq!(card.resident.keys().collect::<Vec<_>>(), ["music-a"]);
|
||||
assert!(card.unloads.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_model_with_requests_in_flight_is_not_evicted() {
|
||||
let card = Arc::new(Mutex::new(Card::default()));
|
||||
let neuron = spawn_neuron(Arc::clone(&card)).await;
|
||||
let fleet = gateway(&neuron, "in-flight").await;
|
||||
card.lock().unwrap().in_flight.insert("music-a".into(), 1);
|
||||
seed_resident(&fleet, &card, &neuron, "music-a").await;
|
||||
|
||||
let b = cortex_gateway::router::resolve(&fleet, "music-b").await;
|
||||
|
||||
assert!(matches!(b, Err(RouteError::NodeBusy { .. })), "{b:?}");
|
||||
let card = card.lock().unwrap();
|
||||
assert!(card.unloads.is_empty(), "evicted a busy model");
|
||||
assert!(!card.overcommitted);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn an_idle_model_is_swapped_out() {
|
||||
let card = Arc::new(Mutex::new(Card::default()));
|
||||
let neuron = spawn_neuron(Arc::clone(&card)).await;
|
||||
let fleet = gateway(&neuron, "idle").await;
|
||||
seed_resident(&fleet, &card, &neuron, "music-a").await;
|
||||
|
||||
let b = cortex_gateway::router::resolve(&fleet, "music-b").await;
|
||||
|
||||
assert!(b.is_ok(), "{b:?}");
|
||||
let card = card.lock().unwrap();
|
||||
assert_eq!(card.unloads, ["music-a"]);
|
||||
assert_eq!(card.resident.keys().collect::<Vec<_>>(), ["music-b"]);
|
||||
assert!(!card.overcommitted);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_cold_load_waits_for_the_neuron_s_own_startup_load() {
|
||||
// The neuron has just restarted and is loading its default model.
|
||||
// That load is in no model list yet and its memory has barely
|
||||
// started to fill, so the card still looks empty.
|
||||
let card = Arc::new(Mutex::new(Card {
|
||||
activating: Some("music-a".into()),
|
||||
..Default::default()
|
||||
}));
|
||||
let neuron = spawn_neuron(Arc::clone(&card)).await;
|
||||
let fleet = gateway(&neuron, "activating").await;
|
||||
|
||||
let b = cortex_gateway::router::resolve(&fleet, "music-b").await;
|
||||
|
||||
assert!(matches!(b, Err(RouteError::NodeBusy { .. })), "{b:?}");
|
||||
assert!(
|
||||
card.lock().unwrap().loads.is_empty(),
|
||||
"loaded over a startup load"
|
||||
);
|
||||
|
||||
// Once the startup load finishes and its model is idle, the swap
|
||||
// goes ahead as usual.
|
||||
{
|
||||
let mut c = card.lock().unwrap();
|
||||
c.activating = None;
|
||||
}
|
||||
seed_resident(&fleet, &card, &neuron, "music-a").await;
|
||||
let b = cortex_gateway::router::resolve(&fleet, "music-b").await;
|
||||
assert!(b.is_ok(), "{b:?}");
|
||||
let card = card.lock().unwrap();
|
||||
assert_eq!(card.unloads, ["music-a"]);
|
||||
assert!(!card.overcommitted);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Using decisions
|
||||
sidebar_label: Decisions
|
||||
description: Typed questions over text or JSON, answered with calibrated probabilities — the System-One endpoint and its playground.
|
||||
description: Typed questions over text or JSON, answered with calibrated probabilities — the System-One endpoint and the Decisions page.
|
||||
---
|
||||
|
||||
# Using decisions
|
||||
@@ -13,7 +13,7 @@ request. That makes it fast (tens of milliseconds), cheap, and
|
||||
predictable enough to put in front of a workflow: routing a ticket,
|
||||
screening a message, deciding whether to escalate.
|
||||
|
||||
Try it without writing code in [the playground](/playground).
|
||||
Try it without writing code on [the Decisions page](/decisions).
|
||||
|
||||
The endpoint is `POST https://helexa.ai/v1/systemone`, and it speaks the
|
||||
TypeSafe Jev protocol, so existing Jev clients work unchanged.
|
||||
|
||||
+304
-16
@@ -1888,53 +1888,209 @@ html[dir="rtl"] .hx-landing-composer textarea {
|
||||
/* -- Music (/music) -------------------------------------------------------- */
|
||||
|
||||
/* The music page reuses the image form controls; these cover what images
|
||||
* has no counterpart for: labelled text areas and a job list. */
|
||||
* has no counterpart for. */
|
||||
.hx-music-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
gap: 0.35rem;
|
||||
min-width: 0;
|
||||
font-size: 0.82rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.hx-music-field textarea,
|
||||
.hx-music input[type="number"] {
|
||||
.hx-music-field textarea {
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
background-color: var(--color-bg-elevated);
|
||||
color: var(--color-text);
|
||||
padding: 0.45rem 0.6rem;
|
||||
padding: 0.55rem 0.7rem;
|
||||
font: inherit;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.5;
|
||||
min-height: 36px;
|
||||
resize: vertical;
|
||||
}
|
||||
.hx-music input[type="number"] {
|
||||
width: 7rem;
|
||||
/* Style beside lyrics on a wide screen: lyrics are short lines, so a
|
||||
* full-width box wastes most of its width. Both boxes share one height. */
|
||||
.hx-music-words {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
.hx-music-jobs {
|
||||
@media (min-width: 768px) {
|
||||
.hx-music-words.two {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
}
|
||||
.hx-music-words.two .hx-music-field textarea {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
.hx-music-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.hx-music-tag {
|
||||
padding: 0.2rem 0.65rem;
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: 999px;
|
||||
background: none;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.78rem;
|
||||
transition:
|
||||
border-color var(--transition-fast),
|
||||
color var(--transition-fast),
|
||||
background-color var(--transition-fast);
|
||||
}
|
||||
.hx-music-tag:hover:not(:disabled) {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-text);
|
||||
}
|
||||
.hx-music-tag.on {
|
||||
border-color: var(--color-accent);
|
||||
background-color: var(--color-accent-soft);
|
||||
color: var(--color-text);
|
||||
}
|
||||
.hx-music-length {
|
||||
flex: 1 1 14rem;
|
||||
max-width: 22rem;
|
||||
}
|
||||
.hx-music-length strong {
|
||||
color: var(--color-text);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.hx-music-length input[type="range"] {
|
||||
width: 100%;
|
||||
accent-color: var(--color-accent);
|
||||
}
|
||||
/* A disabled generate button must read as unavailable at a glance, not
|
||||
* as a slightly faded gradient. */
|
||||
.hx-music .hx-music-generate:disabled {
|
||||
background: var(--color-bg-subtle);
|
||||
color: var(--color-text-muted);
|
||||
box-shadow: inset 0 0 0 1px var(--color-border-subtle);
|
||||
opacity: 1;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
/* Phone widths: the history button sits over the title's start. */
|
||||
@media (max-width: 768px) {
|
||||
.hx-music > h1 {
|
||||
padding-inline-start: 2.75rem;
|
||||
}
|
||||
}
|
||||
.hx-music-blocker {
|
||||
margin: -0.4rem 0 0;
|
||||
text-align: end;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.hx-music-spin {
|
||||
animation: hx-music-turn 0.9s linear infinite;
|
||||
}
|
||||
@keyframes hx-music-turn {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* The studio: what is happening now, below the form. Empty on arrival. */
|
||||
.hx-music-studio {
|
||||
margin-top: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.hx-music-job {
|
||||
padding: 0.7rem 0.8rem;
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
.hx-music-studio:empty {
|
||||
display: none;
|
||||
}
|
||||
.hx-music-activity {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.8rem 1rem;
|
||||
border: 1px dashed var(--color-border-strong);
|
||||
border-radius: 0.75rem;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.hx-music-job {
|
||||
display: grid;
|
||||
grid-template-columns: 5.5rem minmax(0, 1fr);
|
||||
gap: 1rem;
|
||||
padding: 0.9rem;
|
||||
border: 1px solid var(--color-border-subtle);
|
||||
border-radius: 0.9rem;
|
||||
background-color: var(--color-bg-elevated);
|
||||
}
|
||||
.hx-music-cover {
|
||||
width: 5.5rem;
|
||||
aspect-ratio: 1;
|
||||
border-radius: 0.6rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 6px 18px rgb(0 0 0 / 0.25);
|
||||
}
|
||||
/* A record's label and groove sheen over the gradient. */
|
||||
.hx-music-cover::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 18%;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgb(255 255 255 / 0.35) 0 12%, transparent 13% 100%),
|
||||
repeating-radial-gradient(circle, rgb(0 0 0 / 0.12) 0 1px, transparent 1px 4px);
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
.hx-music-cover .hx-music-eq {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
color: #fff;
|
||||
height: 1.6rem;
|
||||
}
|
||||
.hx-music-job-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.hx-music-job header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.hx-music-title {
|
||||
margin: 0;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hx-music-close {
|
||||
flex-shrink: 0;
|
||||
margin-block-start: -0.25rem;
|
||||
}
|
||||
.hx-music-prompt {
|
||||
margin: 0;
|
||||
font-size: 0.85rem;
|
||||
color: var(--color-text-muted);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hx-music-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.6rem;
|
||||
align-items: baseline;
|
||||
gap: 0.25rem 0.75rem;
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
.hx-music-status {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
.hx-music-running .hx-music-status,
|
||||
.hx-music-queued .hx-music-status {
|
||||
@@ -1951,9 +2107,135 @@ html[dir="rtl"] .hx-landing-composer textarea {
|
||||
.hx-music-job .hx-image-card-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
.hx-music-job {
|
||||
grid-template-columns: 3.5rem minmax(0, 1fr);
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.hx-music-cover {
|
||||
width: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Equaliser bars: the "something is being made" signal. */
|
||||
.hx-music-eq {
|
||||
display: inline-flex;
|
||||
align-items: flex-end;
|
||||
gap: 3px;
|
||||
height: 1.1rem;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
.hx-music-eq i {
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
border-radius: 2px;
|
||||
background: currentColor;
|
||||
transform-origin: bottom;
|
||||
animation: hx-music-bounce 0.9s ease-in-out infinite;
|
||||
}
|
||||
.hx-music-eq i:nth-child(2) {
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
.hx-music-eq i:nth-child(3) {
|
||||
animation-delay: -0.6s;
|
||||
}
|
||||
.hx-music-eq i:nth-child(4) {
|
||||
animation-delay: -0.15s;
|
||||
}
|
||||
@keyframes hx-music-bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: scaleY(0.25);
|
||||
}
|
||||
50% {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* The player: a round play button, a waveform that is the seek bar, the
|
||||
* time. */
|
||||
.hx-music-player {
|
||||
width: min(100%, 24rem);
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-block: 0.25rem;
|
||||
}
|
||||
.hx-music-play {
|
||||
flex-shrink: 0;
|
||||
width: 2.6rem;
|
||||
height: 2.6rem;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: var(--gradient-helix);
|
||||
color: #fff;
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
.hx-music-play:hover:not(:disabled) {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
html[dir="rtl"] .hx-music-play svg {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
.hx-music-wave {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
height: 2.6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.hx-music-wave i {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
border-radius: 1px;
|
||||
background-color: var(--color-border-strong);
|
||||
transition: background-color 80ms linear;
|
||||
}
|
||||
.hx-music-wave i.on {
|
||||
background-color: var(--color-accent);
|
||||
}
|
||||
.hx-music-player.standin .hx-music-wave i {
|
||||
opacity: 0.55;
|
||||
}
|
||||
.hx-music-wave:focus-visible {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 3px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.hx-music-time {
|
||||
flex-shrink: 0;
|
||||
font-size: 0.78rem;
|
||||
color: var(--color-text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.hx-music-spinner {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 50%;
|
||||
border: 2px solid rgb(255 255 255 / 0.4);
|
||||
border-top-color: #fff;
|
||||
animation: hx-music-turn 0.8s linear infinite;
|
||||
}
|
||||
@media (max-width: 575.98px) {
|
||||
.hx-music-time {
|
||||
display: none;
|
||||
}
|
||||
.hx-music-wave {
|
||||
gap: 1px;
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.hx-music-eq i,
|
||||
.hx-music-spin,
|
||||
.hx-music-spinner {
|
||||
animation: none;
|
||||
}
|
||||
.hx-music-eq i {
|
||||
transform: scaleY(0.6);
|
||||
}
|
||||
}
|
||||
|
||||
/* History sidebar (#402): the chat sidebar, with collapsible sections. */
|
||||
@@ -2009,6 +2291,12 @@ html[dir="rtl"] .hx-flip-rtl {
|
||||
.hx-music-dot-failed {
|
||||
background-color: var(--color-accent-hot);
|
||||
}
|
||||
.hx-music-swatch {
|
||||
flex-shrink: 0;
|
||||
width: 0.9rem;
|
||||
height: 0.9rem;
|
||||
border-radius: 0.2rem;
|
||||
}
|
||||
@keyframes hx-music-pulse {
|
||||
50% {
|
||||
opacity: 0.25;
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function App() {
|
||||
<Route path="/chat" element={<Chat />} />
|
||||
<Route path="/images" element={<Images />} />
|
||||
<Route path="/music" element={<Music />} />
|
||||
<Route path="/playground" element={<Playground />} />
|
||||
<Route path="/decisions" element={<Playground />} />
|
||||
{/* Documentation. The splat route lets the sidebar tree be
|
||||
arbitrarily deep without enumerating routes here — the
|
||||
page resolves the slug against the content tree. */}
|
||||
|
||||
@@ -115,12 +115,12 @@ const Header: React.FC = () => {
|
||||
{t("nav.music")}
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to="/playground"
|
||||
to="/decisions"
|
||||
className={({ isActive }): string =>
|
||||
isActive ? "nav-link active" : "nav-link"
|
||||
}
|
||||
>
|
||||
{t("nav.playground")}
|
||||
{t("nav.decisions")}
|
||||
</NavLink>
|
||||
{/* Documentation is reachable on every host — this only
|
||||
decides whether it is advertised. The pages are still
|
||||
|
||||
@@ -64,9 +64,9 @@ describe("decisions translations", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it.each(Object.keys(COMMON))("%s names the playground in the nav", (lang) => {
|
||||
it.each(Object.keys(COMMON))("%s names the decisions page in the nav", (lang) => {
|
||||
const nav = COMMON[lang].nav as Tree;
|
||||
expect(typeof nav.playground).toBe("string");
|
||||
expect((nav.playground as string).trim()).not.toBe("");
|
||||
expect(typeof nav.decisions).toBe("string");
|
||||
expect((nav.decisions as string).trim()).not.toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "ውይይት",
|
||||
"images": "ምስሎች",
|
||||
"music": "ሙዚቃ",
|
||||
"playground": "የሙከራ ሜዳ",
|
||||
"decisions": "ውሳኔዎች",
|
||||
"mission": "ተልእኮ",
|
||||
"login": "ይግቡ",
|
||||
"register": "ይመዝገቡ",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "የሙከራ ሜዳ",
|
||||
"lead": "ለውሳኔ ሞዴል ስለማንኛውም ጽሑፍ ወይም JSON ዓይነት ያላቸው ጥያቄዎችን ይጠይቁ፤ በአንድ ዙር የተስተካከሉ መልሶችን ያግኙ — ያለ የመነጨ ጽሑፍ።",
|
||||
"title": "ውሳኔዎች",
|
||||
"lead": "ስለማንኛውም ጽሑፍ ወይም JSON አዎ/አይደለም፣ ባለብዙ ምርጫ ወይም የነጥብ ጥያቄዎችን ይጠይቁ፤ በተስተካከለ እርግጠኝነት ፈጣን መልሶችን ያግኙ። እንደዚህ ያሉ ሞዴሎች አንዳንድ ጊዜ System One ሞዴሎች ይባላሉ።",
|
||||
"docsLink": "እንዴት እንደሚሠራ",
|
||||
"side": "ውጤቶች፣ ምሳሌዎች እና ኮድ",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "ያለፉት 7 ቀናት",
|
||||
"month": "ያለፉት 30 ቀናት",
|
||||
"older": "የቆዩ"
|
||||
}
|
||||
},
|
||||
"length": "ርዝመት",
|
||||
"tags": "የስልት ሃሳቦች",
|
||||
"submitting": "ዘፈንዎ ወደ ስቱዲዮ እየተላከ ነው…",
|
||||
"needStyle": "ዘፈን ለመፍጠር ስልት ይግለጹ።",
|
||||
"needLength": "ከ{{min}} እስከ {{max}} ሰከንድ ያለ ርዝመት ይምረጡ።",
|
||||
"close": "ዝጋ",
|
||||
"pause": "ለአፍታ አቁም",
|
||||
"seek": "በዘፈኑ ውስጥ ያለ ቦታ"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "المحادثة",
|
||||
"images": "الصور",
|
||||
"music": "الموسيقى",
|
||||
"playground": "ساحة التجربة",
|
||||
"decisions": "القرارات",
|
||||
"mission": "المهمة",
|
||||
"login": "تسجيل الدخول",
|
||||
"register": "إنشاء حساب",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "ساحة التجربة",
|
||||
"lead": "اطرح على نموذج قرارات أسئلة محدّدة النوع عن أي نص أو JSON، واحصل على إجابات معايَرة في تمريرة واحدة — دون نص مولَّد.",
|
||||
"title": "القرارات",
|
||||
"lead": "اطرح أسئلة بنعم/لا أو اختيار من متعدد أو تقييم حول أي نص أو JSON، واحصل فورًا على إجابات بثقة معايَرة. تُسمّى هذه النماذج أحيانًا نماذج System One.",
|
||||
"docsLink": "كيف يعمل",
|
||||
"side": "النتائج والأمثلة والشيفرة",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "آخر 7 أيام",
|
||||
"month": "آخر 30 يومًا",
|
||||
"older": "أقدم"
|
||||
}
|
||||
},
|
||||
"length": "المدة",
|
||||
"tags": "أفكار للأسلوب",
|
||||
"submitting": "جارٍ إرسال أغنيتك إلى الاستوديو…",
|
||||
"needStyle": "صِف أسلوبًا لإنشاء أغنية.",
|
||||
"needLength": "اختر مدة بين {{min}} و{{max}} ثانية.",
|
||||
"close": "إغلاق",
|
||||
"pause": "إيقاف مؤقت",
|
||||
"seek": "الموضع في الأغنية"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Чат",
|
||||
"images": "Изображения",
|
||||
"music": "Музика",
|
||||
"playground": "Пясъчник",
|
||||
"decisions": "Решения",
|
||||
"mission": "Мисия",
|
||||
"login": "Вход",
|
||||
"register": "Регистрация",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Пясъчник",
|
||||
"lead": "Задавайте на модел за вземане на решения типизирани въпроси за всякакъв текст или JSON и получавайте калибрирани отговори с едно преминаване — без генериран текст.",
|
||||
"title": "Решения",
|
||||
"lead": "Задавайте въпроси с да/не, с избор между възможности или с оценка за всякакъв текст или JSON и получавайте мигновено отговори с калибрирана увереност. Такива модели понякога се наричат модели System One.",
|
||||
"docsLink": "Как работи",
|
||||
"side": "Резултати, примери и код",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Последните 7 дни",
|
||||
"month": "Последните 30 дни",
|
||||
"older": "По-стари"
|
||||
}
|
||||
},
|
||||
"length": "Продължителност",
|
||||
"tags": "Идеи за стил",
|
||||
"submitting": "Изпращане на песента ви към студиото…",
|
||||
"needStyle": "Опишете стил, за да генерирате песен.",
|
||||
"needLength": "Изберете продължителност между {{min}} и {{max}} секунди.",
|
||||
"close": "Затвори",
|
||||
"pause": "Пауза",
|
||||
"seek": "Позиция в песента"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Xat",
|
||||
"images": "Imatges",
|
||||
"music": "Música",
|
||||
"playground": "Espai de proves",
|
||||
"decisions": "Decisions",
|
||||
"mission": "Missió",
|
||||
"login": "Inicia la sessió",
|
||||
"register": "Registra't",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Espai de proves",
|
||||
"lead": "Feu a un model de decisió preguntes tipades sobre qualsevol text o JSON i obtingueu respostes calibrades en una sola passada, sense text generat.",
|
||||
"title": "Decisions",
|
||||
"lead": "Feu preguntes de sí/no, d’opció múltiple o de puntuació sobre qualsevol text o JSON i obteniu a l’instant respostes amb una confiança calibrada. De vegades se’n diu models System One.",
|
||||
"docsLink": "Com funciona",
|
||||
"side": "Resultats, exemples i codi",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Darrers 7 dies",
|
||||
"month": "Darrers 30 dies",
|
||||
"older": "Més antics"
|
||||
}
|
||||
},
|
||||
"length": "Durada",
|
||||
"tags": "Idees d’estil",
|
||||
"submitting": "S’està enviant la cançó a l’estudi…",
|
||||
"needStyle": "Descriviu un estil per generar una cançó.",
|
||||
"needLength": "Trieu una durada entre {{min}} i {{max}} segons.",
|
||||
"close": "Tanca",
|
||||
"pause": "Pausa",
|
||||
"seek": "Posició a la cançó"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Chat",
|
||||
"images": "Obrázky",
|
||||
"music": "Hudba",
|
||||
"playground": "Pískoviště",
|
||||
"decisions": "Rozhodnutí",
|
||||
"mission": "Mise",
|
||||
"login": "Přihlásit se",
|
||||
"register": "Registrovat se",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Pískoviště",
|
||||
"lead": "Pokládejte rozhodovacímu modelu typované otázky o libovolném textu nebo JSON a dostávejte kalibrované odpovědi v jediném průchodu — bez generovaného textu.",
|
||||
"title": "Rozhodnutí",
|
||||
"lead": "Ptejte se otázkami ano/ne, s výběrem z možností nebo s hodnocením na libovolný text či JSON a okamžitě získejte odpovědi s kalibrovanou jistotou. Takovým modelům se někdy říká modely System One.",
|
||||
"docsLink": "Jak to funguje",
|
||||
"side": "Výsledky, příklady a kód",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Posledních 7 dní",
|
||||
"month": "Posledních 30 dní",
|
||||
"older": "Starší"
|
||||
}
|
||||
},
|
||||
"length": "Délka",
|
||||
"tags": "Nápady na styl",
|
||||
"submitting": "Odesílám skladbu do studia…",
|
||||
"needStyle": "Popište styl, abyste mohli vygenerovat skladbu.",
|
||||
"needLength": "Zvolte délku mezi {{min}} a {{max}} sekundami.",
|
||||
"close": "Zavřít",
|
||||
"pause": "Pozastavit",
|
||||
"seek": "Pozice ve skladbě"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Chat",
|
||||
"images": "Billeder",
|
||||
"music": "Musik",
|
||||
"playground": "Legeplads",
|
||||
"decisions": "Beslutninger",
|
||||
"mission": "Mission",
|
||||
"login": "Log ind",
|
||||
"register": "Opret konto",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Legeplads",
|
||||
"lead": "Stil en beslutningsmodel typede spørgsmål om enhver tekst eller JSON, og få kalibrerede svar i ét gennemløb — uden genereret tekst.",
|
||||
"title": "Beslutninger",
|
||||
"lead": "Stil ja/nej-, multiple choice- eller pointspørgsmål om enhver tekst eller JSON, og få straks svar med kalibreret sikkerhed. Den slags modeller kaldes nogle gange System One-modeller.",
|
||||
"docsLink": "Sådan virker det",
|
||||
"side": "Resultater, eksempler og kode",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Seneste 7 dage",
|
||||
"month": "Seneste 30 dage",
|
||||
"older": "Ældre"
|
||||
}
|
||||
},
|
||||
"length": "Længde",
|
||||
"tags": "Stilidéer",
|
||||
"submitting": "Sender din sang til studiet…",
|
||||
"needStyle": "Beskriv en stil for at generere en sang.",
|
||||
"needLength": "Vælg en længde mellem {{min}} og {{max}} sekunder.",
|
||||
"close": "Luk",
|
||||
"pause": "Pause",
|
||||
"seek": "Position i sangen"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Chat",
|
||||
"images": "Bilder",
|
||||
"music": "Musik",
|
||||
"playground": "Playground",
|
||||
"decisions": "Entscheidungen",
|
||||
"mission": "Mission",
|
||||
"login": "Anmelden",
|
||||
"register": "Registrieren",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Playground",
|
||||
"lead": "Stellen Sie einem Entscheidungsmodell typisierte Fragen zu beliebigem Text oder JSON und erhalten Sie kalibrierte Antworten in einem Durchlauf – ganz ohne generierten Text.",
|
||||
"title": "Entscheidungen",
|
||||
"lead": "Stellen Sie Ja/Nein-, Auswahl- oder Bewertungsfragen zu beliebigem Text oder JSON und erhalten Sie sofort Antworten mit kalibrierter Konfidenz. Solche Modelle werden manchmal System-One-Modelle genannt.",
|
||||
"docsLink": "So funktioniert es",
|
||||
"side": "Ergebnisse, Beispiele und Code",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Letzte 7 Tage",
|
||||
"month": "Letzte 30 Tage",
|
||||
"older": "Älter"
|
||||
}
|
||||
},
|
||||
"length": "Länge",
|
||||
"tags": "Stilideen",
|
||||
"submitting": "Ihr Song wird ans Studio gesendet…",
|
||||
"needStyle": "Beschreiben Sie einen Stil, um einen Song zu erzeugen.",
|
||||
"needLength": "Wählen Sie eine Länge zwischen {{min}} und {{max}} Sekunden.",
|
||||
"close": "Schließen",
|
||||
"pause": "Pause",
|
||||
"seek": "Position im Song"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Συνομιλία",
|
||||
"images": "Εικόνες",
|
||||
"music": "Μουσική",
|
||||
"playground": "Πεδίο δοκιμών",
|
||||
"decisions": "Αποφάσεις",
|
||||
"mission": "Αποστολή",
|
||||
"login": "Σύνδεση",
|
||||
"register": "Εγγραφή",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Πεδίο δοκιμών",
|
||||
"lead": "Κάντε σε ένα μοντέλο αποφάσεων τυποποιημένες ερωτήσεις για οποιοδήποτε κείμενο ή JSON και λάβετε βαθμονομημένες απαντήσεις σε ένα μόνο πέρασμα — χωρίς παραγόμενο κείμενο.",
|
||||
"title": "Αποφάσεις",
|
||||
"lead": "Κάντε ερωτήσεις ναι/όχι, πολλαπλής επιλογής ή βαθμολόγησης για οποιοδήποτε κείμενο ή JSON και λάβετε αμέσως απαντήσεις με βαθμονομημένη βεβαιότητα. Τέτοια μοντέλα ονομάζονται μερικές φορές μοντέλα System One.",
|
||||
"docsLink": "Πώς λειτουργεί",
|
||||
"side": "Αποτελέσματα, παραδείγματα και κώδικας",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Τελευταίες 7 ημέρες",
|
||||
"month": "Τελευταίες 30 ημέρες",
|
||||
"older": "Παλαιότερα"
|
||||
}
|
||||
},
|
||||
"length": "Διάρκεια",
|
||||
"tags": "Ιδέες ύφους",
|
||||
"submitting": "Αποστολή του τραγουδιού σας στο στούντιο…",
|
||||
"needStyle": "Περιγράψτε ένα ύφος για να δημιουργήσετε τραγούδι.",
|
||||
"needLength": "Επιλέξτε διάρκεια από {{min}} έως {{max}} δευτερόλεπτα.",
|
||||
"close": "Κλείσιμο",
|
||||
"pause": "Παύση",
|
||||
"seek": "Θέση στο τραγούδι"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Chat",
|
||||
"images": "Images",
|
||||
"music": "Music",
|
||||
"playground": "Playground",
|
||||
"decisions": "Decisions",
|
||||
"mission": "Mission",
|
||||
"login": "Sign in",
|
||||
"register": "Sign up",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Playground",
|
||||
"lead": "Ask a decision model typed questions about any text or JSON, and get calibrated answers in one pass — no generated text.",
|
||||
"title": "Decisions",
|
||||
"lead": "Ask yes/no, multiple-choice or scoring questions about any text or JSON, and get instant answers with calibrated confidence. Models like this are sometimes called System One models.",
|
||||
"docsLink": "How it works",
|
||||
"side": "Results, examples and code",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Previous 7 days",
|
||||
"month": "Previous 30 days",
|
||||
"older": "Older"
|
||||
}
|
||||
},
|
||||
"length": "Length",
|
||||
"tags": "Style ideas",
|
||||
"submitting": "Sending your song to the studio…",
|
||||
"needStyle": "Describe a style to generate a song.",
|
||||
"needLength": "Choose a length between {{min}} and {{max}} seconds.",
|
||||
"close": "Close",
|
||||
"pause": "Pause",
|
||||
"seek": "Position in the song"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Chat",
|
||||
"images": "Imágenes",
|
||||
"music": "Música",
|
||||
"playground": "Zona de pruebas",
|
||||
"decisions": "Decisiones",
|
||||
"mission": "Misión",
|
||||
"login": "Iniciar sesión",
|
||||
"register": "Registrarse",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Zona de pruebas",
|
||||
"lead": "Haga a un modelo de decisión preguntas tipadas sobre cualquier texto o JSON y obtenga respuestas calibradas en una sola pasada, sin texto generado.",
|
||||
"title": "Decisiones",
|
||||
"lead": "Haga preguntas de sí/no, de opción múltiple o de puntuación sobre cualquier texto o JSON y obtenga al instante respuestas con una confianza calibrada. A estos modelos a veces se les llama modelos System One.",
|
||||
"docsLink": "Cómo funciona",
|
||||
"side": "Resultados, ejemplos y código",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Últimos 7 días",
|
||||
"month": "Últimos 30 días",
|
||||
"older": "Anteriores"
|
||||
}
|
||||
},
|
||||
"length": "Duración",
|
||||
"tags": "Ideas de estilo",
|
||||
"submitting": "Enviando su canción al estudio…",
|
||||
"needStyle": "Describa un estilo para generar una canción.",
|
||||
"needLength": "Elija una duración entre {{min}} y {{max}} segundos.",
|
||||
"close": "Cerrar",
|
||||
"pause": "Pausa",
|
||||
"seek": "Posición en la canción"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Vestlus",
|
||||
"images": "Pildid",
|
||||
"music": "Muusika",
|
||||
"playground": "Mänguväljak",
|
||||
"decisions": "Otsused",
|
||||
"mission": "Missioon",
|
||||
"login": "Logi sisse",
|
||||
"register": "Registreeru",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Mänguväljak",
|
||||
"lead": "Esitage otsustusmudelile tüübitud küsimusi mis tahes teksti või JSON-i kohta ja saage kalibreeritud vastused ühe läbimisega — ilma genereeritud tekstita.",
|
||||
"title": "Otsused",
|
||||
"lead": "Esita jah/ei-, valikvastustega või hindamisküsimusi mis tahes teksti või JSON-i kohta ja saa kohe vastused kalibreeritud kindlusega. Selliseid mudeleid nimetatakse mõnikord System One mudeliteks.",
|
||||
"docsLink": "Kuidas see töötab",
|
||||
"side": "Tulemused, näited ja kood",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Viimased 7 päeva",
|
||||
"month": "Viimased 30 päeva",
|
||||
"older": "Vanemad"
|
||||
}
|
||||
},
|
||||
"length": "Pikkus",
|
||||
"tags": "Stiiliideed",
|
||||
"submitting": "Saadan su laulu stuudiosse…",
|
||||
"needStyle": "Laulu loomiseks kirjelda stiili.",
|
||||
"needLength": "Vali pikkus vahemikus {{min}}–{{max}} sekundit.",
|
||||
"close": "Sulge",
|
||||
"pause": "Paus",
|
||||
"seek": "Asukoht laulus"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "گفتگو",
|
||||
"images": "تصاویر",
|
||||
"music": "موسیقی",
|
||||
"playground": "زمین آزمایش",
|
||||
"decisions": "تصمیمها",
|
||||
"mission": "مأموریت",
|
||||
"login": "ورود",
|
||||
"register": "ثبتنام",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "زمین آزمایش",
|
||||
"lead": "از یک مدل تصمیمگیری دربارهٔ هر متن یا JSON پرسشهای نوعدار بپرسید و در یک گذر پاسخهای کالیبرهشده بگیرید — بدون متن تولیدشده.",
|
||||
"title": "تصمیمها",
|
||||
"lead": "دربارهٔ هر متن یا JSON پرسشهای بله/خیر، چندگزینهای یا امتیازدهی بپرسید و فوراً پاسخهایی با اطمینان کالیبرهشده بگیرید. به چنین مدلهایی گاهی مدلهای System One میگویند.",
|
||||
"docsLink": "چطور کار میکند",
|
||||
"side": "نتایج، نمونهها و کد",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "۷ روز گذشته",
|
||||
"month": "۳۰ روز گذشته",
|
||||
"older": "قدیمیتر"
|
||||
}
|
||||
},
|
||||
"length": "مدت",
|
||||
"tags": "ایدههای سبک",
|
||||
"submitting": "در حال ارسال آهنگ شما به استودیو…",
|
||||
"needStyle": "برای ساخت آهنگ، یک سبک توصیف کنید.",
|
||||
"needLength": "مدتی بین {{min}} تا {{max}} ثانیه انتخاب کنید.",
|
||||
"close": "بستن",
|
||||
"pause": "مکث",
|
||||
"seek": "موقعیت در آهنگ"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Keskustelu",
|
||||
"images": "Kuvat",
|
||||
"music": "Musiikki",
|
||||
"playground": "Hiekkalaatikko",
|
||||
"decisions": "Päätökset",
|
||||
"mission": "Missio",
|
||||
"login": "Kirjaudu sisään",
|
||||
"register": "Rekisteröidy",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Hiekkalaatikko",
|
||||
"lead": "Esitä päätösmallille tyypitettyjä kysymyksiä mistä tahansa tekstistä tai JSONista ja saat kalibroidut vastaukset yhdellä läpikäynnillä — ilman tuotettua tekstiä.",
|
||||
"title": "Päätökset",
|
||||
"lead": "Esitä kyllä/ei-, monivalinta- tai pisteytyskysymyksiä mistä tahansa tekstistä tai JSONista ja saat heti vastaukset kalibroidulla varmuudella. Tällaisia malleja kutsutaan joskus System One -malleiksi.",
|
||||
"docsLink": "Miten se toimii",
|
||||
"side": "Tulokset, esimerkit ja koodi",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Edelliset 7 päivää",
|
||||
"month": "Edelliset 30 päivää",
|
||||
"older": "Vanhemmat"
|
||||
}
|
||||
},
|
||||
"length": "Pituus",
|
||||
"tags": "Tyyli-ideoita",
|
||||
"submitting": "Kappaletta lähetetään studioon…",
|
||||
"needStyle": "Kuvaile tyyli luodaksesi kappaleen.",
|
||||
"needLength": "Valitse pituus väliltä {{min}}–{{max}} sekuntia.",
|
||||
"close": "Sulje",
|
||||
"pause": "Tauko",
|
||||
"seek": "Kohta kappaleessa"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Chat",
|
||||
"images": "Images",
|
||||
"music": "Musique",
|
||||
"playground": "Bac à sable",
|
||||
"decisions": "Décisions",
|
||||
"mission": "Mission",
|
||||
"login": "Connexion",
|
||||
"register": "Inscription",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Bac à sable",
|
||||
"lead": "Posez à un modèle de décision des questions typées sur n’importe quel texte ou JSON, et obtenez des réponses calibrées en une seule passe — sans texte généré.",
|
||||
"title": "Décisions",
|
||||
"lead": "Posez des questions oui/non, à choix multiples ou de notation sur n’importe quel texte ou JSON, et obtenez instantanément des réponses avec une confiance calibrée. On appelle parfois ces modèles des modèles System One.",
|
||||
"docsLink": "Comment ça marche",
|
||||
"side": "Résultats, exemples et code",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "7 derniers jours",
|
||||
"month": "30 derniers jours",
|
||||
"older": "Plus anciens"
|
||||
}
|
||||
},
|
||||
"length": "Durée",
|
||||
"tags": "Idées de style",
|
||||
"submitting": "Envoi de votre chanson au studio…",
|
||||
"needStyle": "Décrivez un style pour générer une chanson.",
|
||||
"needLength": "Choisissez une durée entre {{min}} et {{max}} secondes.",
|
||||
"close": "Fermer",
|
||||
"pause": "Pause",
|
||||
"seek": "Position dans la chanson"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Comhrá",
|
||||
"images": "Íomhánna",
|
||||
"music": "Ceol",
|
||||
"playground": "Clós súgartha",
|
||||
"decisions": "Cinntí",
|
||||
"mission": "Misean",
|
||||
"login": "Sínigh isteach",
|
||||
"register": "Cláraigh",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Clós súgartha",
|
||||
"lead": "Cuir ceisteanna clóscríofa ar mhúnla cinntí faoi théacs nó JSON ar bith agus faigh freagraí calabraithe in aon phas amháin — gan téacs ginte.",
|
||||
"title": "Cinntí",
|
||||
"lead": "Cuir ceisteanna tá/níl, ilrogha nó scórála faoi théacs nó JSON ar bith agus faigh freagraí láithreacha le muinín calabraithe. Uaireanta tugtar samhlacha System One ar shamhlacha mar seo.",
|
||||
"docsLink": "Conas a oibríonn sé",
|
||||
"side": "Torthaí, samplaí agus cód",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Na 7 lá seo caite",
|
||||
"month": "Na 30 lá seo caite",
|
||||
"older": "Níos sine"
|
||||
}
|
||||
},
|
||||
"length": "Fad",
|
||||
"tags": "Smaointe stíle",
|
||||
"submitting": "Do d’amhrán á sheoladh chuig an stiúideo…",
|
||||
"needStyle": "Déan cur síos ar stíl chun amhrán a ghiniúint.",
|
||||
"needLength": "Roghnaigh fad idir {{min}} agus {{max}} soicind.",
|
||||
"close": "Dún",
|
||||
"pause": "Sos",
|
||||
"seek": "Suíomh san amhrán"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Tattaunawa",
|
||||
"images": "Hotuna",
|
||||
"music": "Kiɗa",
|
||||
"playground": "Filin gwaji",
|
||||
"decisions": "Shawarwari",
|
||||
"mission": "Manufa",
|
||||
"login": "Shiga",
|
||||
"register": "Yi rajista",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Filin gwaji",
|
||||
"lead": "Yi wa samfurin yanke shawara tambayoyi masu nau’i game da kowane rubutu ko JSON, ka sami amsoshi da aka daidaita a zagaye ɗaya — ba tare da rubutun da aka ƙirƙira ba.",
|
||||
"title": "Shawarwari",
|
||||
"lead": "Yi tambayoyin e/a'a, na zaɓi daga zaɓuɓɓuka ko na maki game da kowane rubutu ko JSON, ka sami amsoshi nan take tare da tabbaci da aka daidaita. A wasu lokuta ana kiran irin waɗannan samfura samfuran System One.",
|
||||
"docsLink": "Yadda yake aiki",
|
||||
"side": "Sakamako, misalai da lamba",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Kwanaki 7 da suka gabata",
|
||||
"month": "Kwanaki 30 da suka gabata",
|
||||
"older": "Tsofaffi"
|
||||
}
|
||||
},
|
||||
"length": "Tsawo",
|
||||
"tags": "Ra'ayoyin salo",
|
||||
"submitting": "Ana aika wakarka zuwa sitiriyo…",
|
||||
"needStyle": "Bayyana salo don ƙirƙirar waƙa.",
|
||||
"needLength": "Zaɓi tsawo tsakanin daƙiƙa {{min}} da {{max}}.",
|
||||
"close": "Rufe",
|
||||
"pause": "Dakata",
|
||||
"seek": "Matsayi a cikin waƙa"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "צ'אט",
|
||||
"images": "תמונות",
|
||||
"music": "מוזיקה",
|
||||
"playground": "ארגז חול",
|
||||
"decisions": "החלטות",
|
||||
"mission": "חזון",
|
||||
"login": "התחברות",
|
||||
"register": "הרשמה",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "ארגז חול",
|
||||
"lead": "שאלו מודל החלטות שאלות מוקלדות על כל טקסט או JSON וקבלו תשובות מכוילות במעבר אחד — בלי טקסט מחולל.",
|
||||
"title": "החלטות",
|
||||
"lead": "שאלו שאלות כן/לא, רב־ברירה או דירוג על כל טקסט או JSON וקבלו מיד תשובות עם ודאות מכוילת. מודלים כאלה נקראים לפעמים מודלי System One.",
|
||||
"docsLink": "איך זה עובד",
|
||||
"side": "תוצאות, דוגמאות וקוד",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "7 הימים האחרונים",
|
||||
"month": "30 הימים האחרונים",
|
||||
"older": "ישנים יותר"
|
||||
}
|
||||
},
|
||||
"length": "אורך",
|
||||
"tags": "רעיונות לסגנון",
|
||||
"submitting": "השיר שלך נשלח לאולפן…",
|
||||
"needStyle": "תארו סגנון כדי ליצור שיר.",
|
||||
"needLength": "בחרו אורך בין {{min}} ל־{{max}} שניות.",
|
||||
"close": "סגירה",
|
||||
"pause": "השהיה",
|
||||
"seek": "מיקום בשיר"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Razgovor",
|
||||
"images": "Slike",
|
||||
"music": "Glazba",
|
||||
"playground": "Igralište",
|
||||
"decisions": "Odluke",
|
||||
"mission": "Misija",
|
||||
"login": "Prijava",
|
||||
"register": "Registracija",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Igralište",
|
||||
"lead": "Postavite modelu za odlučivanje tipizirana pitanja o bilo kojem tekstu ili JSON-u i dobijte kalibrirane odgovore u jednom prolazu — bez generiranog teksta.",
|
||||
"title": "Odluke",
|
||||
"lead": "Postavljajte pitanja da/ne, s višestrukim izborom ili s ocjenjivanjem o bilo kojem tekstu ili JSON-u i odmah dobijte odgovore s kalibriranom pouzdanošću. Takvi se modeli ponekad nazivaju modelima System One.",
|
||||
"docsLink": "Kako radi",
|
||||
"side": "Rezultati, primjeri i kod",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Zadnjih 7 dana",
|
||||
"month": "Zadnjih 30 dana",
|
||||
"older": "Starije"
|
||||
}
|
||||
},
|
||||
"length": "Trajanje",
|
||||
"tags": "Ideje za stil",
|
||||
"submitting": "Šaljem vašu pjesmu u studio…",
|
||||
"needStyle": "Opišite stil kako biste generirali pjesmu.",
|
||||
"needLength": "Odaberite trajanje između {{min}} i {{max}} sekundi.",
|
||||
"close": "Zatvori",
|
||||
"pause": "Pauza",
|
||||
"seek": "Položaj u pjesmi"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Csevegés",
|
||||
"images": "Képek",
|
||||
"music": "Zene",
|
||||
"playground": "Játszótér",
|
||||
"decisions": "Döntések",
|
||||
"mission": "Küldetés",
|
||||
"login": "Bejelentkezés",
|
||||
"register": "Regisztráció",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Játszótér",
|
||||
"lead": "Tegyen fel egy döntési modellnek típusos kérdéseket bármilyen szövegről vagy JSON-ról, és kapjon kalibrált válaszokat egyetlen lépésben — generált szöveg nélkül.",
|
||||
"title": "Döntések",
|
||||
"lead": "Tegyen fel igen/nem, feleletválasztós vagy pontozó kérdéseket bármilyen szövegről vagy JSON-ról, és azonnal kalibrált megbízhatóságú válaszokat kap. Az ilyen modelleket néha System One modelleknek nevezik.",
|
||||
"docsLink": "Hogyan működik",
|
||||
"side": "Eredmények, példák és kód",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Elmúlt 7 nap",
|
||||
"month": "Elmúlt 30 nap",
|
||||
"older": "Régebbi"
|
||||
}
|
||||
},
|
||||
"length": "Hossz",
|
||||
"tags": "Stílusötletek",
|
||||
"submitting": "A dal küldése a stúdióba…",
|
||||
"needStyle": "Írjon le egy stílust a dal létrehozásához.",
|
||||
"needLength": "Válasszon {{min}} és {{max}} másodperc közötti hosszt.",
|
||||
"close": "Bezárás",
|
||||
"pause": "Szünet",
|
||||
"seek": "Pozíció a dalban"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Chat",
|
||||
"images": "Immagini",
|
||||
"music": "Musica",
|
||||
"playground": "Playground",
|
||||
"decisions": "Decisioni",
|
||||
"mission": "Missione",
|
||||
"login": "Accedi",
|
||||
"register": "Registrati",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Playground",
|
||||
"lead": "Poni a un modello decisionale domande tipizzate su qualsiasi testo o JSON e ottieni risposte calibrate in un solo passaggio, senza testo generato.",
|
||||
"title": "Decisioni",
|
||||
"lead": "Fai domande sì/no, a scelta multipla o di punteggio su qualsiasi testo o JSON e ottieni subito risposte con una confidenza calibrata. Modelli di questo tipo sono talvolta chiamati modelli System One.",
|
||||
"docsLink": "Come funziona",
|
||||
"side": "Risultati, esempi e codice",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Ultimi 7 giorni",
|
||||
"month": "Ultimi 30 giorni",
|
||||
"older": "Meno recenti"
|
||||
}
|
||||
},
|
||||
"length": "Durata",
|
||||
"tags": "Idee di stile",
|
||||
"submitting": "Invio della canzone allo studio…",
|
||||
"needStyle": "Descrivi uno stile per generare una canzone.",
|
||||
"needLength": "Scegli una durata tra {{min}} e {{max}} secondi.",
|
||||
"close": "Chiudi",
|
||||
"pause": "Pausa",
|
||||
"seek": "Posizione nella canzone"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "ჩატი",
|
||||
"images": "სურათები",
|
||||
"music": "მუსიკა",
|
||||
"playground": "სათამაშო მოედანი",
|
||||
"decisions": "გადაწყვეტილებები",
|
||||
"mission": "მისია",
|
||||
"login": "შესვლა",
|
||||
"register": "რეგისტრაცია",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "სათამაშო მოედანი",
|
||||
"lead": "დაუსვით გადაწყვეტილების მოდელს ტიპიზებული კითხვები ნებისმიერ ტექსტსა თუ JSON-ზე და მიიღეთ კალიბრირებული პასუხები ერთ გავლაში — გენერირებული ტექსტის გარეშე.",
|
||||
"title": "გადაწყვეტილებები",
|
||||
"lead": "დასვით კი/არა, არჩევითი ან შეფასების კითხვები ნებისმიერ ტექსტზე ან JSON-ზე და მყისიერად მიიღეთ პასუხები დაკალიბრებული სანდოობით. ასეთ მოდელებს ზოგჯერ System One მოდელებს უწოდებენ.",
|
||||
"docsLink": "როგორ მუშაობს",
|
||||
"side": "შედეგები, მაგალითები და კოდი",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "ბოლო 7 დღე",
|
||||
"month": "ბოლო 30 დღე",
|
||||
"older": "უფრო ძველი"
|
||||
}
|
||||
},
|
||||
"length": "ხანგრძლივობა",
|
||||
"tags": "სტილის იდეები",
|
||||
"submitting": "თქვენი სიმღერა სტუდიაში იგზავნება…",
|
||||
"needStyle": "სიმღერის შესაქმნელად აღწერეთ სტილი.",
|
||||
"needLength": "აირჩიეთ ხანგრძლივობა {{min}}-დან {{max}} წამამდე.",
|
||||
"close": "დახურვა",
|
||||
"pause": "პაუზა",
|
||||
"seek": "პოზიცია სიმღერაში"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Чат",
|
||||
"images": "Кескіндер",
|
||||
"music": "Музыка",
|
||||
"playground": "Сынақ алаңы",
|
||||
"decisions": "Шешімдер",
|
||||
"mission": "Миссия",
|
||||
"login": "Кіру",
|
||||
"register": "Тіркелу",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Сынақ алаңы",
|
||||
"lead": "Шешім моделіне кез келген мәтін не JSON туралы типтелген сұрақтар қойып, бір өтуде калибрленген жауаптар алыңыз — мәтін генерацияланбайды.",
|
||||
"title": "Шешімдер",
|
||||
"lead": "Кез келген мәтін немесе JSON туралы иә/жоқ, таңдау немесе бағалау сұрақтарын қойып, калибрленген сенімділікпен лезде жауап алыңыз. Мұндай модельдерді кейде System One модельдері деп атайды.",
|
||||
"docsLink": "Қалай жұмыс істейді",
|
||||
"side": "Нәтижелер, мысалдар және код",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Соңғы 7 күн",
|
||||
"month": "Соңғы 30 күн",
|
||||
"older": "Ескірек"
|
||||
}
|
||||
},
|
||||
"length": "Ұзақтығы",
|
||||
"tags": "Стиль идеялары",
|
||||
"submitting": "Әніңіз студияға жіберілуде…",
|
||||
"needStyle": "Ән жасау үшін стильді сипаттаңыз.",
|
||||
"needLength": "{{min}} мен {{max}} секунд аралығындағы ұзақтықты таңдаңыз.",
|
||||
"close": "Жабу",
|
||||
"pause": "Кідірту",
|
||||
"seek": "Әндегі орын"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Pokalbis",
|
||||
"images": "Vaizdai",
|
||||
"music": "Muzika",
|
||||
"playground": "Smėlio dėžė",
|
||||
"decisions": "Sprendimai",
|
||||
"mission": "Misija",
|
||||
"login": "Prisijungti",
|
||||
"register": "Registruotis",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Smėlio dėžė",
|
||||
"lead": "Užduokite sprendimų modeliui tipizuotus klausimus apie bet kokį tekstą ar JSON ir gaukite kalibruotus atsakymus vienu perėjimu — be sugeneruoto teksto.",
|
||||
"title": "Sprendimai",
|
||||
"lead": "Užduokite taip/ne, pasirinkimo ar vertinimo klausimus apie bet kokį tekstą ar JSON ir iškart gaukite atsakymus su kalibruotu patikimumu. Tokie modeliai kartais vadinami System One modeliais.",
|
||||
"docsLink": "Kaip tai veikia",
|
||||
"side": "Rezultatai, pavyzdžiai ir kodas",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Paskutinės 7 dienos",
|
||||
"month": "Paskutinės 30 dienų",
|
||||
"older": "Senesni"
|
||||
}
|
||||
},
|
||||
"length": "Trukmė",
|
||||
"tags": "Stiliaus idėjos",
|
||||
"submitting": "Jūsų daina siunčiama į studiją…",
|
||||
"needStyle": "Aprašykite stilių, kad sukurtumėte dainą.",
|
||||
"needLength": "Pasirinkite trukmę nuo {{min}} iki {{max}} sekundžių.",
|
||||
"close": "Uždaryti",
|
||||
"pause": "Pristabdyti",
|
||||
"seek": "Vieta dainoje"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Tērzēšana",
|
||||
"images": "Attēli",
|
||||
"music": "Mūzika",
|
||||
"playground": "Smilškaste",
|
||||
"decisions": "Lēmumi",
|
||||
"mission": "Misija",
|
||||
"login": "Pieteikties",
|
||||
"register": "Reģistrēties",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Smilškaste",
|
||||
"lead": "Uzdodiet lēmumu modelim tipizētus jautājumus par jebkuru tekstu vai JSON un saņemiet kalibrētas atbildes vienā piegājienā — bez ģenerēta teksta.",
|
||||
"title": "Lēmumi",
|
||||
"lead": "Uzdodiet jā/nē, atbilžu izvēles vai vērtēšanas jautājumus par jebkuru tekstu vai JSON un nekavējoties saņemiet atbildes ar kalibrētu ticamību. Šādus modeļus dažkārt sauc par System One modeļiem.",
|
||||
"docsLink": "Kā tas darbojas",
|
||||
"side": "Rezultāti, piemēri un kods",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Pēdējās 7 dienas",
|
||||
"month": "Pēdējās 30 dienas",
|
||||
"older": "Vecāki"
|
||||
}
|
||||
},
|
||||
"length": "Ilgums",
|
||||
"tags": "Stila idejas",
|
||||
"submitting": "Sūta jūsu dziesmu uz studiju…",
|
||||
"needStyle": "Aprakstiet stilu, lai ģenerētu dziesmu.",
|
||||
"needLength": "Izvēlieties ilgumu no {{min}} līdz {{max}} sekundēm.",
|
||||
"close": "Aizvērt",
|
||||
"pause": "Pauze",
|
||||
"seek": "Pozīcija dziesmā"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "المحادثة",
|
||||
"images": "Tiwlafin",
|
||||
"music": "الموسيقى",
|
||||
"playground": "بلاصة التجريب",
|
||||
"decisions": "القرارات",
|
||||
"mission": "المهمة",
|
||||
"login": "دخول",
|
||||
"register": "تسجيل",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "بلاصة التجريب",
|
||||
"lead": "سوّل نموذج ديال القرارات أسئلة محددة النوع على أي نص ولا JSON، وخود أجوبة معايرة فدوزة وحدة — بلا نص مولّد.",
|
||||
"title": "القرارات",
|
||||
"lead": "سوّل أسئلة ب إيه/لا، ولا بالاختيار، ولا بالتنقيط على أي نص ولا JSON، وخود أجوبة فالحين بثقة معايرة. هاد النماذج كيتسماو شي مرات نماذج System One.",
|
||||
"docsLink": "كيفاش خدام",
|
||||
"side": "النتائج، الأمثلة والكود",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "آخر 7 يام",
|
||||
"month": "آخر 30 يوم",
|
||||
"older": "القدام"
|
||||
}
|
||||
},
|
||||
"length": "الطول",
|
||||
"tags": "أفكار ديال الستيل",
|
||||
"submitting": "كنصيفطو الأغنية ديالك للستوديو…",
|
||||
"needStyle": "وصف شي ستيل باش تصايب أغنية.",
|
||||
"needLength": "ختار طول بين {{min}} و{{max}} ثانية.",
|
||||
"close": "سد",
|
||||
"pause": "وقّف",
|
||||
"seek": "البلاصة فالأغنية"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Chat",
|
||||
"images": "Immaġni",
|
||||
"music": "Mużika",
|
||||
"playground": "Spazju tal-provi",
|
||||
"decisions": "Deċiżjonijiet",
|
||||
"mission": "Missjoni",
|
||||
"login": "Idħol",
|
||||
"register": "Irreġistra",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Spazju tal-provi",
|
||||
"lead": "Staqsi lil mudell tad-deċiżjonijiet mistoqsijiet tipizzati dwar kwalunkwe test jew JSON u ikseb tweġibiet kalibrati f’passaġġ wieħed — mingħajr test iġġenerat.",
|
||||
"title": "Deċiżjonijiet",
|
||||
"lead": "Staqsi mistoqsijiet iva/le, b’għażla multipla jew ta’ punteġġ dwar kwalunkwe test jew JSON, u ikseb tweġibiet immedjati b’kunfidenza kkalibrata. Mudelli bħal dawn xi drabi jissejħu mudelli System One.",
|
||||
"docsLink": "Kif jaħdem",
|
||||
"side": "Riżultati, eżempji u kodiċi",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "L-aħħar 7 ijiem",
|
||||
"month": "L-aħħar 30 jum",
|
||||
"older": "Eqdem"
|
||||
}
|
||||
},
|
||||
"length": "Tul",
|
||||
"tags": "Ideat tal-istil",
|
||||
"submitting": "Qed nibagħtu l-kanzunetta tiegħek lill-istudjo…",
|
||||
"needStyle": "Iddeskrivi stil biex tiġġenera kanzunetta.",
|
||||
"needLength": "Agħżel tul bejn {{min}} u {{max}} sekonda.",
|
||||
"close": "Agħlaq",
|
||||
"pause": "Pawża",
|
||||
"seek": "Pożizzjoni fil-kanzunetta"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Chat",
|
||||
"images": "Afbeeldingen",
|
||||
"music": "Muziek",
|
||||
"playground": "Playground",
|
||||
"decisions": "Beslissingen",
|
||||
"mission": "Missie",
|
||||
"login": "Inloggen",
|
||||
"register": "Registreren",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Playground",
|
||||
"lead": "Stel een beslismodel getypeerde vragen over elke tekst of JSON en krijg gekalibreerde antwoorden in één keer — zonder gegenereerde tekst.",
|
||||
"title": "Beslissingen",
|
||||
"lead": "Stel ja/nee-, meerkeuze- of scorevragen over willekeurige tekst of JSON en krijg direct antwoorden met gekalibreerde zekerheid. Zulke modellen worden soms System One-modellen genoemd.",
|
||||
"docsLink": "Hoe het werkt",
|
||||
"side": "Resultaten, voorbeelden en code",
|
||||
"tabs": {
|
||||
|
||||
@@ -41,5 +41,13 @@
|
||||
"week": "Afgelopen 7 dagen",
|
||||
"month": "Afgelopen 30 dagen",
|
||||
"older": "Ouder"
|
||||
}
|
||||
},
|
||||
"length": "Lengte",
|
||||
"tags": "Stijlideeën",
|
||||
"submitting": "Uw nummer wordt naar de studio gestuurd…",
|
||||
"needStyle": "Beschrijf een stijl om een nummer te genereren.",
|
||||
"needLength": "Kies een lengte tussen {{min}} en {{max}} seconden.",
|
||||
"close": "Sluiten",
|
||||
"pause": "Pauzeren",
|
||||
"seek": "Positie in het nummer"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"chat": "Chat",
|
||||
"images": "Bilder",
|
||||
"music": "Musikk",
|
||||
"playground": "Lekeplass",
|
||||
"decisions": "Beslutninger",
|
||||
"mission": "Misjon",
|
||||
"login": "Logg inn",
|
||||
"register": "Registrer deg",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Lekeplass",
|
||||
"lead": "Still en beslutningsmodell typede spørsmål om hvilken som helst tekst eller JSON, og få kalibrerte svar i én enkelt runde — uten generert tekst.",
|
||||
"title": "Beslutninger",
|
||||
"lead": "Still ja/nei-, flervalgs- eller poengspørsmål om hvilken som helst tekst eller JSON, og få umiddelbare svar med kalibrert sikkerhet. Slike modeller kalles noen ganger System One-modeller.",
|
||||
"docsLink": "Slik fungerer det",
|
||||
"side": "Resultater, eksempler og kode",
|
||||
"tabs": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user