Towniestyles.css11 matches
682background-color: var(--highlight);
683}
684.card-image {
685display: flex;
686align-items: center;
704}
705706.image-placeholder,
707.image-thumbnail {
708width: 40px;
709height: 40px;
711object-fit: cover;
712}
713.image-placeholder {
714background-color: var(--muted);
715}
722}
723724.image-row {
725display: flex;
726gap: var(--space-1);
727}
728.input-image {
729position: relative;
730border: 1px solid var(--muted);
731border-radius: 6px;
732}
733.remove-image-button {
734position: absolute;
735top: 0;
744opacity: 0;
745}
746.input-image:hover .remove-image-button {
747opacity: 1;
748}
749750.image-drop-overlay {
751position: fixed;
752top: 0;
761justify-content: center;
762}
763.image-drop-inner {
764padding: var(--space-2);
765background-color: var(--background);
766}
767768.transition, .input-box, .icon-button, .button, .remove-image-button {
769transition-property: color, background-color, border-color, opacity;
770transition-duration: 200ms;
Towniesend-message.ts12 matches
20}
2122const { messages, project, branchId, anthropicApiKey, selectedFiles, images } = await c.req.json();
23// console.log("Original messages:", JSON.stringify(messages, null, 2));
24// console.log("Images received:", JSON.stringify(images, null, 2));
2526const apiKey = anthropicApiKey || Deno.env.get("ANTHROPIC_API_KEY");
39let coreMessages = convertToCoreMessages(messages);
4041// If there are images, we need to add them to the last user message
42if (images && Array.isArray(images) && images.length > 0) {
43// Find the last user message
44const lastUserMessageIndex = coreMessages.findIndex(
62};
6364// Add each image to the content array using the correct ImagePart format
65for (const image of images) {
66if (image && image.url) {
67// Extract mime type from data URL if available
68let mimeType = undefined;
69if (image.url.startsWith("data:")) {
70const matches = image.url.match(/^data:([^;]+);/);
71if (matches && matches.length > 1) {
72mimeType = matches[1];
7576newUserMessage.content.push({
77type: "image",
78image: image.url,
79mimeType,
80});
163branch_id: branchId,
164val_id: project.id,
165num_images: images?.length || 0,
166model,
167finish_reason: result.finishReason,
Townieschema.tsx2 matches
16price: number;
17finish_reason: string;
18num_images: number;
19our_api_token: boolean;
20}
36price REAL,
37finish_reason TEXT,
38num_images INTEGER,
39our_api_token INTEGER NOT NULL
40)
Towniequeries.tsx4 matches
54model,
55finish_reason,
56num_images,
57our_api_token,
58}: {
66model: string;
67finish_reason: string;
68num_images: number;
69our_api_token: boolean;
70}) {
90price,
91finish_reason,
92num_images,
93our_api_token
94) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
107price,
108finish_reason,
109num_images,
110our_api_token ? 1 : 0,
111],
TownieProjectsRoute.tsx7 matches
48user: {
49username: string;
50profileImageUrl: string|null;
51};
52project: any;
55<div className="card">
5657{project.imageUrl ? (
58<img src={project.imageUrl} className="card-image" />
59) : user.profileImageUrl ? (
60<div className="card-image">
61<img
62src={user.profileImageUrl}
63width="48"
64height="48"
67</div>
68) : (
69<div className="card-image placeholder" />
70)}
71<div className="card-body">
TownieInputBox.tsx46 matches
2import { useRef, useState, useEffect } from "https://esm.sh/react@18.2.0?dev";
3import { PlusIcon, ArrowUpIcon, Square, XIcon } from "./icons.tsx";
4import { processFiles } from "../utils/images.ts";
56export function InputBox ({
11running,
12error,
13images,
14setImages,
15} : {
16value: string;
20running: boolean;
21error: any;
22images: (string|null)[];
23setImages: (images: (string|null)[]) => void;
24}) {
25const form = useRef(null);
57autoFocus={true}
58/>
59<ImageRow images={images} setImages={setImages} />
60<div className="toolbar">
61<UploadButton
62disabled={running}
63images={images}
64setImages={setImages}
65/>
66<div className="spacer" />
88}
8990export function ImageDropContainer ({
91images,
92setImages,
93running,
94children,
95}: {
96images: (string|null)[];
97setImages: (images: (string|null)[]) => void;
98running: boolean;
99children: React.ReactNode;
100}) {
101const dragging = useImageDrop({ images, setImages, running });
102103return (
105{children}
106{dragging && (
107<div className="image-drop-overlay">
108<div className="image-drop-inner">
109Drop images here to upload
110</div>
111</div>
115}
116117export function useImageDrop ({ images, setImages, running }: {
118images: (string|null)[];
119setImages(images: (string|null)[]) => void;
120running: boolean;
121}) {
143setDragging(false);
144if (e.dataTransfer?.files && !running) {
145processFiles(Array.from(e.dataTransfer.files), images, setImages);
146}
147}
164}
165166function ImageRow ({ images, setImages }: {
167images: (string|null)[];
168setImages: (images: (string|null)[]) => void;
169}) {
170return (
171<div className="image-row">
172{images.map((image, i) => (
173<Thumbnail
174key={i}
175image={image}
176onRemove={() => {
177setImages([
178...images.slice(0, i),
179...images.slice(i + 1),
180]);
181}}
186}
187188function Thumbnail ({ image, onRemove }: {
189image: string|null;
190onRemove: () => void;
191}) {
192if (!image) return null;
193194return (
195<div className="input-image">
196<img
197src={image}
198alt="User uploaded image"
199className="image-thumbnail"
200/>
201<button
202type="button"
203title="Remove image"
204className="remove-image-button"
205onClick={onRemove}
206>
212213function UploadButton ({
214images,
215setImages,
216disabled,
217}: {
218images: (string|null)[];
219setImages: (images: (string|null)[]) => void;
220disabled: boolean;
221}) {
226<button
227type="button"
228title="Upload image"
229disabled={disabled}
230onClick={e => {
234<PlusIcon />
235<div className="sr-only">
236Upload image
237</div>
238</button>
243onChange={e => {
244if (e.target.files) {
245processFiles(Array.from(e.target.files), images, setImages);
246}
247}}
12export const PROMPT_IMAGE_LIMIT = 5;
34export const processFiles = async (files: File[], images: (string | null)[], setImages: (images: (string | null)[]) => void) => {
5const imageFiles = files.filter(file => file.type.startsWith('image/'));
6const filesToProcess = imageFiles.slice(0, PROMPT_IMAGE_LIMIT - images.filter(Boolean).length);
78if (filesToProcess.length === 0) return;
910const newImages = [...images, ...Array(filesToProcess.length).fill(null)];
11setImages(newImages);
1213const processedImages = await Promise.all(
14filesToProcess.map(async (file) => {
15return await readFileAsDataURL(file);
17);
1819const updatedImages = [...images];
20processedImages.forEach((dataUrl, index) => {
21updatedImages[images.length + index] = dataUrl;
22});
2324setImages(updatedImages.slice(0, PROMPT_IMAGE_LIMIT));
25};
2630reader.onload = () => {
31const result = reader.result as string;
32console.log("Image loaded, size:", result.length, "bytes");
33resolve(result);
34};
Towniefavicon.http.tsx1 match
13return new Response(svg, {
14headers: {
15"Content-Type": "image/svg+xml",
16},
17});
TownieChatRoute.tsx13 matches
10import { useUsageStats } from "../hooks/useUsageStats.ts";
11import { Messages } from "./Messages.tsx";
12import { InputBox, ImageDropContainer } from "./InputBox.tsx";
13import { PreviewFrame } from "./PreviewFrame.tsx";
14import { BranchSelect } from "./BranchSelect.tsx";
64}) {
65const { token, anthropicApiKey } = useAuth();
66const [images, setImages] = useState<(string|null)[]>([]);
67const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
68const { audio } = useContext(AppContext);
84bearerToken: token,
85selectedFiles,
86images,
87soundEnabled: audio,
88});
108109return (
110<ImageDropContainer
111running={running}
112images={images}
113setImages={setImages}>
114<div
115className="chat-container container">
131onSubmit={e => {
132handleSubmit(e);
133setImages([]);
134}}
135onCancel={handleStop}
136running={running}
137error={error}
138images={images}
139setImages={setImages}
140/>
141</div>
149rel="norefferer"
150className="block-link text-link lockup">
151{project.imageUrl ? (
152<img src={project.imageUrl} className="image-thumbnail" />
153) : (
154<div className="image-placeholder" />
155)}
156<div>
173</div>
174<pre hidden>{JSON.stringify(messages, null, 2)}</pre>
175</ImageDropContainer>
176);
177}
3Feel free to mess around with this val and make it your own :). Just click on "Fork" in the top right.
45You can change the phrases that show up as you click no, you can change the firstImg and secondImg, maybe even add more images. And you can also change the colors and any of the text on the screen!
67Have fun with it and hopefully your crush says yes hehe.