> ## Documentation Index
> Fetch the complete documentation index at: https://fal.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Composer and references

> Reference models, generations, assets, characters, skills, and connected apps from one palette. Attach files, drag and drop, and paste.

export const MentionRailComp = () => {
  const FAL_COMP_W = 754;
  const FAL_COMP_H = 338;
  const falEases = {
    lin: t => t,
    out: t => 1 - Math.pow(1 - t, 3),
    expo: t => t >= 1 ? 1 : 1 - Math.pow(2, -10 * t),
    inOut: t => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
    back: t => 1 + 3.1 * Math.pow(t - 1, 3) + 2.1 * Math.pow(t - 1, 2)
  };
  const falEvalNum = (tr, t) => {
    const k = tr.keys;
    const n = k.length;
    if (t <= k[0][0]) return k[0][1];
    if (t >= k[n - 1][0]) return k[n - 1][1];
    for (let i = 0; i < n - 1; i++) {
      const t0 = k[i][0];
      const t1 = k[i + 1][0];
      if (t >= t0 && t <= t1) {
        const u = t1 === t0 ? 1 : (t - t0) / (t1 - t0);
        return k[i][1] + (k[i + 1][1] - k[i][1]) * tr.ease(u);
      }
    }
    return k[n - 1][1];
  };
  const falMakeTimeline = root => {
    const tracks = [];
    const q = sel => !sel ? [] : typeof sel === "string" ? [...root.querySelectorAll(sel)] : [sel];
    const track = (sel, prop, keys, ease = "out", fmt = null) => {
      const made = q(sel).map(el => ({
        el,
        prop,
        keys,
        ease: falEases[ease],
        fmt
      }));
      tracks.push(...made);
      return made;
    };
    const enter = (sel, at, o = {}) => {
      const {y = 10, x = 0, s = 1, b = 0, d = 0.55, e = "expo"} = o;
      track(sel, "--o", [[at, 0], [at + d * 0.6, 1]]);
      if (y) track(sel, "--y", [[at, y], [at + d, 0]], e);
      if (x) track(sel, "--x", [[at, x], [at + d, 0]], e);
      if (s !== 1) track(sel, "--s", [[at, s], [at + d, 1]], e);
      if (b) track(sel, "--b", [[at, b], [at + d * 0.8, 0]], e);
    };
    const exit = (sel, at, d = 0.45) => track(sel, "--o", [[at, 1], [at + d, 0]], "inOut");
    const typewrite = (el, str, at, rate) => {
      const end = at + str.length * rate;
      track(el, "text", [[at, 0], [end, str.length]], "lin", n => str.slice(0, Math.round(n)));
      return end;
    };
    const sample = t => {
      for (const tr of tracks) {
        const v = falEvalNum(tr, t);
        if (tr.prop === "text" && tr.fmt) {
          const s = tr.fmt(v);
          if (tr.el.textContent !== s) tr.el.textContent = s;
        } else if (tr.prop === "opacity") {
          tr.el.style.opacity = String(v);
        } else {
          tr.el.style.setProperty(tr.prop, String(v));
        }
      }
    };
    return {
      track,
      enter,
      exit,
      typewrite,
      sample
    };
  };
  const useFalComp = build => {
    const stageRef = useRef(null);
    const rootRef = useRef(null);
    useEffect(() => {
      const stage = stageRef.current;
      const root = rootRef.current;
      if (!stage || !root || typeof window === "undefined") return;
      const ro = new ResizeObserver(([entry]) => {
        const width = entry && entry.contentRect.width;
        if (width) stage.style.setProperty("--fal-comp-scale", String(width / FAL_COMP_W));
      });
      ro.observe(stage);
      const tl = falMakeTimeline(root);
      const {duration, still, onReady} = build(tl, root);
      const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
      let disposed = false;
      const ready = Promise.all([...[...root.querySelectorAll("img")].map(im => (im.decode ? im.decode() : Promise.resolve()).catch(() => undefined)), document.fonts ? document.fonts.ready : Promise.resolve()]);
      if (reduced) {
        if (onReady) onReady();
        tl.sample(still);
        ready.then(() => {
          if (disposed) return;
          if (onReady) onReady();
          tl.sample(still);
        });
        return () => {
          disposed = true;
          ro.disconnect();
        };
      }
      tl.sample(0);
      let isReady = false;
      let running = false;
      let inView = true;
      let raf = 0;
      let t = 0;
      let clockMs = 0;
      const step = now => {
        const dt = Math.min(0.05, (now - clockMs) / 1000);
        clockMs = now;
        t += dt;
        if (t >= duration) t -= duration;
        tl.sample(t);
        raf = requestAnimationFrame(step);
      };
      const sync = () => {
        const shouldRun = isReady && !disposed && inView && document.visibilityState === "visible";
        if (shouldRun && !running) {
          running = true;
          clockMs = performance.now();
          raf = requestAnimationFrame(step);
        } else if (!shouldRun && running) {
          running = false;
          cancelAnimationFrame(raf);
        }
      };
      const io = new IntersectionObserver(([entry]) => {
        inView = entry ? entry.isIntersecting : true;
        sync();
      });
      io.observe(root);
      document.addEventListener("visibilitychange", sync);
      ready.then(() => {
        if (disposed) return;
        if (onReady) onReady();
        isReady = true;
        sync();
      });
      return () => {
        disposed = true;
        running = false;
        cancelAnimationFrame(raf);
        io.disconnect();
        ro.disconnect();
        document.removeEventListener("visibilitychange", sync);
      };
    }, []);
    return {
      stageRef,
      rootRef
    };
  };
  const FAL_COMP_BASE_CSS = `
@property --x { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --y { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --s { syntax: "<number>"; inherits: false; initial-value: 1; }
@property --o { syntax: "<number>"; inherits: false; initial-value: 1; }
@property --b { syntax: "<number>"; inherits: false; initial-value: 0; }
@property --h { syntax: "<number>"; inherits: false; initial-value: 150; }
.fal-comp-frame { position: relative; width: 100%; aspect-ratio: 754 / 338; border-radius: 12px; overflow: hidden; margin: 1.5rem 0; }
.fal-comp-stage { position: absolute; inset: 0; overflow: hidden; }
.fal-comp-artboard { position: absolute; left: 0; top: 0; width: 754px; height: 338px; transform-origin: 0 0; transform: scale(var(--fal-comp-scale, 1)); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; font-size: 14px; -webkit-font-smoothing: antialiased; color: #202022; }
.fal-comp-artboard * { box-sizing: border-box; margin: 0; }
.fal-comp-artboard img { max-width: none; }
.fal-comp-a { transform: translate3d(calc(var(--x, 0) * 1px), calc(var(--y, 0) * 1px), 0) scale(var(--s, 1)); opacity: var(--o, 1); filter: blur(calc(var(--b, 0) * 1px)); will-change: transform, opacity, filter; }
`;
  const falFrame = ({comp, label, css, plate, children}) => <div className="fal-comp-frame">
    <style>{FAL_COMP_BASE_CSS + css}</style>
    <div ref={comp.stageRef} role="img" aria-label={label} className="fal-comp-stage" style={{
    background: plate
  }}>
      <div ref={comp.rootRef} aria-hidden="true" className="fal-comp-artboard">
        {children}
      </div>
    </div>
  </div>;
  const falStroke = (d, color = "#494950", w = 1.2, size = 12, cap = "square") => <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} fill="none" stroke={color} strokeWidth={w} strokeLinecap={cap} strokeLinejoin="round">
    <path d={d} />
  </svg>;
  const CHARACTERS = [["kai", "f1"], ["luna", "f2"], ["gren", "f3"], ["koori", "f4"], ["tommo", "f5"]];
  const STYLES = ["s5", "s4", "s3", "s2"];
  const PROMPT_A = "Generate ";
  const PROMPT_B = " drifting over the spore fields at dusk, same freckles and build as the reference sheet.";
  const build = (tl, root) => {
    tl.enter("[data-panel]", 0.1, {
      y: 14,
      s: 0.99,
      b: 4,
      d: 0.68
    });
    tl.enter("[data-tabbar]", 0.38, {
      y: 6,
      d: 0.42
    });
    tl.enter("[data-segbar]", 0.48, {
      y: 6,
      d: 0.42
    });
    const secs = [...root.querySelectorAll("[data-sec]")];
    tl.enter(secs[0] || null, 0.6, {
      y: 6,
      d: 0.4
    });
    [...root.querySelectorAll("[data-av]")].forEach((el, i) => tl.enter(el, 0.72 + i * 0.07, {
      y: 7,
      s: 0.9,
      d: 0.46
    }));
    tl.enter(secs[1] || null, 1.1, {
      y: 6,
      d: 0.4
    });
    [...root.querySelectorAll("[data-th]")].forEach((el, i) => tl.enter(el, 1.22 + i * 0.07, {
      y: 7,
      s: 0.95,
      d: 0.46
    }));
    tl.enter("[data-promptcard]", 1.55, {
      y: 12,
      s: 0.99,
      b: 4,
      d: 0.62
    });
    const aEnd = tl.typewrite(root.querySelector("[data-t1]"), PROMPT_A, 1.95, 0.022);
    tl.track("[data-mention]", "--o", [[aEnd + 0.04, 0], [aEnd + 0.2, 1]]);
    tl.track("[data-mention]", "--s", [[aEnd + 0.04, 0.82], [aEnd + 0.46, 1]], "back");
    const last = tl.typewrite(root.querySelector("[data-t2]"), PROMPT_B, aEnd + 0.34, 0.011);
    const OUT = last + 7;
    tl.exit("[data-ui]", OUT, 0.42);
    return {
      duration: OUT + 0.48,
      still: OUT - 1
    };
  };
  const comp = useFalComp(build);
  const A = "/images/agent/comps/";
  const css = `
.mr-ui { position: absolute; inset: 0; opacity: var(--o, 1); }
.mr-panel { position: absolute; left: 43px; top: 48px; width: 476px; height: 316px; display: flex; flex-direction: column; background: #fff; border-radius: 10px; overflow: hidden; box-shadow: 0 0 0 1px rgba(18,18,22,.08), 0 20px 25px -5px rgba(0,0,0,.1), 0 10px 10px -5px rgba(0,0,0,.04); }
.mr-tabbar { height: 40px; flex: none; display: flex; align-items: center; gap: 8px; padding: 0 7.5px; border-bottom: 1px solid rgba(18,18,22,.08); }
.mr-tab { display: inline-flex; align-items: center; gap: 7px; height: 25px; padding: 0 9px; border-radius: 6px; white-space: nowrap; font-size: 14px; font-weight: 500; letter-spacing: -.02em; color: #787881; }
.mr-tab.mr-on { background: #f3f3f3; color: #202022; font-weight: 600; }
.mr-tab svg { display: block; flex: none; }
.mr-panelbtn { margin-left: auto; width: 26px; height: 26px; border-radius: 6px; display: grid; place-items: center; }
.mr-segbar { height: 41px; flex: none; display: flex; align-items: center; gap: 8px; padding: 0 7.5px; }
.mr-seg { display: inline-flex; align-items: center; height: 23px; padding: 0 9px; border-radius: 6px; white-space: nowrap; font-size: 13px; font-weight: 500; letter-spacing: -.02em; color: #787881; }
.mr-seg.mr-on { background: #f3f3f3; color: #202022; font-weight: 600; }
.mr-sec { height: 24px; flex: none; display: flex; align-items: center; gap: 9px; padding-left: 16px; }
.mr-sec svg { display: block; flex: none; }
.mr-sec-title { font-size: 15px; font-weight: 600; letter-spacing: -.02em; color: #202022; }
.mr-sec-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 14px; padding: 0 4px; background: #f3f3f3; border-radius: 4px; font-size: 11px; font-weight: 600; letter-spacing: -.01em; color: #494950; }
.mr-chev { margin-left: auto; margin-right: 12px; display: grid; place-items: center; width: 20px; height: 20px; }
.mr-avatars { flex: none; display: flex; gap: 7.5px; padding-left: 14.5px; margin-top: 6.5px; }
.mr-av { width: 44px; flex: none; display: flex; flex-direction: column; align-items: center; gap: 4px; }
.mr-ring { position: relative; width: 44px; height: 44px; border-radius: 50%; overflow: hidden; background: rgba(18,18,22,.05); box-shadow: inset 0 0 0 1px rgba(18,18,22,.06); }
.mr-ring img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mr-av figcaption { font-size: 12px; font-weight: 500; line-height: 15px; letter-spacing: -.02em; color: #494950; white-space: nowrap; }
.mr-thumbs { flex: none; display: flex; gap: 7.5px; padding-left: 11px; margin-top: 6.5px; }
.mr-th { width: 108px; height: 77px; flex: none; border-radius: 6px; overflow: hidden; background: rgba(18,18,22,.05); box-shadow: inset 0 0 0 1px rgba(18,18,22,.06); }
.mr-th img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mr-promptcard { position: absolute; left: 312px; top: 89.5px; width: 400px; height: 157px; display: flex; flex-direction: column; background: #fff; border-radius: 10px; box-shadow: 0 0 0 1px rgba(18,18,22,.08), 0 20px 25px -5px rgba(0,0,0,.1), 0 10px 10px -5px rgba(0,0,0,.04); }
.mr-pbody { position: relative; height: 114px; padding: 10px 80px 0 17.5px; }
.mr-ptext { font-size: 15px; font-weight: 400; line-height: 25.5px; letter-spacing: -.02em; color: #202022; }
.mr-mention { display: inline-flex; align-items: center; gap: 5px; vertical-align: middle; height: 21px; padding: 0 5px; background: #e7e7e8; border-radius: 5px; }
.mr-mention .mr-at { color: #787881; }
.mr-mention .mr-nm { font-weight: 600; color: #202022; }
.mr-mention img { width: 17px; height: 17px; border-radius: 50%; object-fit: cover; display: block; }
.mr-send { position: absolute; right: 9px; top: 8px; width: 34px; height: 34px; display: grid; place-items: center; background: #202022; border-radius: 7px; }
.mr-ptools { height: 43px; flex: none; display: flex; align-items: center; gap: 8px; padding: 0 17px; border-top: 1px solid rgba(18,18,22,.08); }
.mr-tchip { display: inline-flex; align-items: center; justify-content: center; gap: 6px; flex: none; height: 26px; padding: 0 10px; background: rgba(18,18,22,.05); border-radius: 6px; font-size: 13px; font-weight: 600; letter-spacing: -.01em; color: #19191a; }
.mr-tbtn { width: 26px; height: 26px; border-radius: 6px; display: grid; place-items: center; flex: none; }
`;
  return falFrame({
    comp,
    css,
    plate: "linear-gradient(135deg, #cdeaf0 0%, #bfe4ec 55%, #b2dbe4 100%)",
    label: "The project context rail with reusable characters and collections, and a prompt where a character mention lands as a chip",
    children: <div data-ui className="mr-ui" style={{
      "--o": 0
    }}>
        <div data-panel className="fal-comp-a mr-panel">
          <div data-tabbar className="fal-comp-a mr-tabbar">
            <span className="mr-tab mr-on">{falStroke("M0.55 0.55V9.75H11.59V2.39H6.07L4.84 0.55H0.55Z", "#202022", 1.1, 13, "round")}Project</span>
            <span className="mr-tab">{falStroke("M12.5 5.46C12.5 2.39 10.23 0.55 6.67 0.55C3.11 0.55 0.85 2.39 0.85 5.46C0.85 6.25 1.4 7.6 1.49 7.81C1.57 8.02 1.81 8.88 0.85 10.14C2.14 10.76 3.52 9.75 3.52 9.75C4.47 10.25 5.6 10.36 6.67 10.36C10.23 10.36 12.5 8.52 12.5 5.46Z", "#A6A6AE", 1.1, 13)}This chat</span>
            <span className="mr-panelbtn"><svg width={13} height={13} viewBox="0 0 13 13" fill="none" stroke="#202022" strokeWidth="1.1" strokeLinejoin="round"><rect x="0.9" y="1.9" width="11.2" height="9.2" rx="1.6" /><path d="M8.4 1.9v9.2" /></svg></span>
          </div>
          <div data-segbar className="fal-comp-a mr-segbar">
            {["Context", "All media", "Image", "Video"].map((l, i) => <span key={l} className={"mr-seg" + (i === 0 ? " mr-on" : "")}>{l}</span>)}
          </div>
          <div data-sec className="fal-comp-a mr-sec" style={{
      marginTop: 6
    }}>
            {falStroke("M9.89 10.06C8.9 9.11 7.55 8.52 6.07 8.52C4.59 8.52 3.25 9.11 2.25 10.06M7.91 4.84C7.91 5.86 7.09 6.68 6.07 6.68C5.06 6.68 4.23 5.86 4.23 4.84C4.23 3.83 5.06 3 6.07 3C7.09 3 7.91 3.83 7.91 4.84ZM11.59 6.07C11.59 7.74 10.85 9.23 9.68 10.25C8.71 11.08 7.45 11.59 6.07 11.59C4.69 11.59 3.43 11.08 2.46 10.25C1.29 9.23 0.55 7.74 0.55 6.07C0.55 3.02 3.02 0.55 6.07 0.55C9.12 0.55 11.59 3.02 11.59 6.07Z", "#787881", 1.1, 13)}
            <span className="mr-sec-title">Characters</span><span className="mr-sec-count">7</span>
          </div>
          <div className="mr-avatars">
            {CHARACTERS.map(([name, asset]) => <figure key={name} data-av className="fal-comp-a mr-av">
                <span className="mr-ring"><img src={A + "mr-" + asset + ".webp"} alt="" width={44} height={44} /></span>
                <figcaption>@{name}</figcaption>
              </figure>)}
          </div>
          <div data-sec className="fal-comp-a mr-sec" style={{
      marginTop: 17
    }}>
            {falStroke("M3.25 8.83H3.37M5.7 10.21L9.84 3.04L6.38 1.04M5.46 10.76L11.86 7.08L9.86 3.62M0.55 0.55H6.07V8.83C6.07 10.35 4.84 11.59 3.31 11.59C1.79 11.59 0.55 10.35 0.55 8.83V0.55Z", "#787881", 1.1, 13)}
            <span className="mr-sec-title">Collections</span>
            <span className="mr-chev">{falStroke("M4 5.4 6.5 7.9 9 5.4", "#787881", 1.1, 13)}</span>
          </div>
          <div className="mr-thumbs">
            {STYLES.map(asset => <span key={asset} data-th className="fal-comp-a mr-th"><img src={A + "mr-" + asset + ".webp"} alt="" width={108} height={77} /></span>)}
          </div>
        </div>
        <div data-promptcard className="fal-comp-a mr-promptcard">
          <div className="mr-pbody">
            <p className="mr-ptext">
              <span data-t1 />
              <span data-mention className="fal-comp-a mr-mention"><span className="mr-at">@</span><span className="mr-nm">luna</span><img src={A + "mr-f2.webp"} alt="" width={17} height={17} /></span>
              <span data-t2 />
            </p>
            <span className="mr-send">{falStroke("M9.67 5.4L5.4 1.13L1.13 5.4M5.4 1.84L5.4 12.51", "#FAFAFA", 1.6, 14)}</span>
          </div>
          <div className="mr-ptools">
            <span className="mr-tbtn">{falStroke("M4.37 0.64V8.11M0.64 4.37H8.11", "#494950", 1.28, 9)}</span>
            <span className="mr-tchip">{falStroke("M0.64 0.64V8.64H10.24V2.24H5.44L4.37 0.64H0.64Z", "#494950", 1.28, 11, "round")}Sporelight</span>
            <span className="mr-tchip">{falStroke("M8.54 0C8.54 1.48 7.88 2.13 6.4 2.13C7.88 2.13 8.54 2.79 8.54 4.27M8.54 0C8.54 1.48 9.19 2.13 10.67 2.13C9.19 2.13 8.54 2.79 8.54 4.27M8.54 0V4.27M0 6.13C3.15 6.13 4.53 4.75 4.53 1.6C4.53 4.75 5.92 6.13 9.07 6.13C5.92 6.13 4.53 7.52 4.53 10.67C4.53 7.52 3.15 6.13 0 6.13Z", "#494950", 1.07, 11)}Auto</span>
          </div>
        </div>
      </div>
  });
};

The composer is where you talk to the agent. It supports references through the `@` palette, file attachments, drag and drop, and long-text paste.

## The `@` palette

Type `@` anywhere in the composer, or click the **+** button, to open the palette. It searches models, generations, characters, and skills at once, and it groups results into sections.

| Section          | What it holds                                                         |
| :--------------- | :-------------------------------------------------------------------- |
| **Attach files** | Upload images, video, audio, or ZIP archives                          |
| **Generations**  | Named runs from this chat, to use as the input for the next step      |
| **Models**       | Pin a model, or pick several to compare                               |
| **Assets**       | Your [fal Assets](/docs/documentation/agent/assets-and-characters) library |
| **Characters**   | Reusable identities                                                   |
| **Skills**       | Force a [skill](/docs/documentation/agent/skills) for this turn            |
| **Connectors**   | A [connected app](/docs/documentation/agent/tools/connectors)              |

<Frame>
  <img src="https://mintcdn.com/fal-d8505a2e/DmEWlE6WV9vaz0Me/images/agent/composer-palette.png?fit=max&auto=format&n=DmEWlE6WV9vaz0Me&q=85&s=f8e482d9169b2fca5a2da6fc2ec4711c" alt="The composer palette with its sections: Attach files, Generations, Models, Assets, Characters, Skills, and Connectors" width="1497" height="812" data-path="images/agent/composer-palette.png" />
</Frame>

Use the arrow keys to move, `Enter` to select, `Backspace` to go back to the list, and `Escape` to close. A picked item becomes a chip in the composer.

### Inside a section

Each section opens as a list with its own search. **Generations** shows the named runs from this chat with the model that made them. **Models** lists models with a modality filter. **Skills** lists every enabled skill.

<Frame>
  <img src="https://mintcdn.com/fal-d8505a2e/DmEWlE6WV9vaz0Me/images/agent/palette-generations.png?fit=max&auto=format&n=DmEWlE6WV9vaz0Me&q=85&s=51de3c4987785d4e8ad61c3a5d0d4604" alt="The Generations section of the palette listing two named runs with their model and media type" width="1202" height="972" data-path="images/agent/palette-generations.png" />
</Frame>

<Frame>
  <img src="https://mintcdn.com/fal-d8505a2e/DmEWlE6WV9vaz0Me/images/agent/palette-models.png?fit=max&auto=format&n=DmEWlE6WV9vaz0Me&q=85&s=22d8078a91362c9ca3901e6567e3dd36" alt="The Models section of the palette with a modality filter and a list of models tagged by task" width="1202" height="972" data-path="images/agent/palette-models.png" />
</Frame>

<Frame>
  <img src="https://mintcdn.com/fal-d8505a2e/DmEWlE6WV9vaz0Me/images/agent/palette-skills.png?fit=max&auto=format&n=DmEWlE6WV9vaz0Me&q=85&s=bf15e016aaa8d9af746f05beb2638405" alt="The Skills section of the palette listing enabled skills with their descriptions" width="1202" height="972" data-path="images/agent/palette-skills.png" />
</Frame>

A character chip carries the identity into the prompt, so you do not describe the character again.

<MentionRailComp />

### Generation references

Every completed output gets an ordinal in the order it landed. When the agent names a run, it also gets a readable name, such as `cdg-bag-environments/open-field`. Both appear in the media viewer header. Pick a generation from the palette when you want an output as the input for the next step:

```text theme={null}
Upscale the open-field shot and then animate it with a slow push-in.
```

### Model pins

The model chip under the composer shows **Auto** when no model is pinned. Pick a model from the palette to pin it for the message. Remove the chip to return to Auto. See [Models and generation](/docs/documentation/agent/models-and-generation).

## Attachments

Click the attachment button or drag files into the chat. The agent uses attachments as references, edit sources, or context.

| Type          | Formats                                                                   | Size limit |
| :------------ | :------------------------------------------------------------------------ | :--------- |
| Image         | JPEG, PNG, WebP, GIF                                                      | 50 MB      |
| Video         | MP4, WebM, QuickTime                                                      | 500 MB     |
| Audio         | MP3, WAV, OGG, FLAC, M4A, AAC                                             | 50 MB      |
| Document      | PDF, DOCX, TXT, Markdown, CSV, TSV, JSON                                  | 25 MB      |
| Code and text | YAML, XML, HTML, CSS, JS, TS, Python, Go, Rust, Java, Ruby, shell scripts | 25 MB      |
| Archive       | ZIP, for training datasets                                                | 2 GB       |

A message can carry up to 20 attachments. Uploads retry automatically. Drafts, including attachments, are saved in the browser and survive a reload.

<Note>
  3D files cannot be attached to a chat message. When you drop them on a project, they are saved to the project only.
</Note>

## Drag and drop

Where you drop a file decides where it goes.

| Drop target                     | Result                                                                       |
| :------------------------------ | :--------------------------------------------------------------------------- |
| Chat body                       | The file attaches to the message                                             |
| Media rail, scope **This chat** | The file attaches to the message and is saved to the project library         |
| Media rail, scope **Project**   | The file is saved to the project. A dialog asks **Attach to this chat too?** |
| Project page                    | Documents become project context. Media joins the project's Assets           |

In a chat without a project, files always attach to the message. ZIP files always attach to the chat and never enter a project library.

## Paste

* A pasted **fal media URL** becomes an attachment with a link chip.
* Any other pasted **URL** becomes a link chip. The URL stays in the message so the agent can read the page.
* Pasted **text** longer than 2,000 characters or 25 lines becomes a document attachment named `Pasted text.txt`.

## Send or queue

`Enter` sends the message. If the agent is busy, the message waits and runs next. `Alt+Enter` adds the message to the queue instead, so it runs after the current work. See [Queue and steering](/docs/documentation/agent/chats/queue).
